个人小作品系列,这次继续介绍自己曾经写的一些“小玩意”,这次就介绍下自己初学安卓时,写的一个简仿QQ吧。
里面都是一些基础知识,但是作为初学者来说,还是可以学到很多东西的。
由于这个涉及到的知识很散,就按照界面一个个说,主要就是一些安卓基础,要是你能耐心看下去的话,肯定都能做到。好啦,开始吧。
目录
1.欢迎界面(handler、Timer的使用等)
2.登录界面(属性动画、editText的清除效果、popupwindow的使用、Gif图片的显示等)
3.注册界面(短信功能的使用、Listview、组件间通信)
4.主界面(fragment的使用、自定义dialog、ExpandLIstView的使用)
5.主界面侧拉栏(自定义HorizontalScroView,动画的高级用法)
6.消息电话按钮切换效果(drawable的使用)
7.自定义Toast
8.特殊图片.9.png图片的使用方法与场景
正文
1.欢迎界面
我最终实现的是这个样子,截的QQ欢迎界面原图,然后用拙劣的PS技术签了个名!(现在感觉有点尬,不过记得当时还是觉得挺有意思的,哈哈),这个界面在持续3秒后,便会自动跳转到登录界面。
放图:
实现方式:
使用单独的一个activity,例如我这里为SplashActivity,然后修改程序的入口为SplashActivity
修改程序入口的方法为,在程序入口的activity下面加上下面这段代码,最终这个activity的申明是这个样子
<activity android:name=".Splashactivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
需要注意的是一个应用程序中,只能有一个activity被申明为程序入口,我想这个应该很容易理解,我就不废话啦。
那么怎么实现界面停留3秒,跳转到登录界面的功能呢?这个实现方式有很多种,但是大致思路都是差不多的,可以handler,也可以用定时器(Timer)。我这里使用的第一种方法,相关代码也很简单,调用postDelayed()方法,根据我们要实现的需求这里使用两个参数的重载方法,第一个参数为实现了Runnable接口的对象,第二个参数为时间延迟,第一个对象中,由于Runnable为一个接口,便需要实现该接口下的run方法,在run方法里的代码,将在第二个参数设定的时间延迟后执行。相关代码如下
new Handler().postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub Intent intent = new Intent(Splashactivity.this, QQ.class); startActivity(intent); Splashactivity.this.finish(); } }, 3000);
不过既然是作为学习,我们也来学习下Timer类的使用。我当时这里为什么不用Timer实现也是有原因的,Timer是定时器,既然叫做定时器,那么这个东东实现的效果,是每隔一段时间做一些事情,但是我这里的需要是隔一段时间做一遍相应的事件即可,并不需要做很多遍,不过为什么我说Timer类也可以呢?因为timer既然定时了,肯定是可以取消的,我们只需要在事件执行一遍之后,取消该定时器即可,相关代码如下。
Timer timer = new Timer(); TimerTask task = new TimerTask() { @Override public void run() { try { Intent intent = new Intent(Splashactivity.this, QQ.class); startActivity(intent); Splashactivity.this.finish(); } catch (IOException e) { e.printStackTrace(); } } }; timer.schedule(task,0,10000);当然不要忘了,及时取消定时器哦,在Activity的onStop方法里执行 timer.cancel();
2.登录界面
不过对现在来说,这个界面已经不知道是QQ多少个版本之前的登录界面了,简单的布局就不介绍了,可以在源码里查看
说下这个界面需要注意的地方
(1)界面初次进入,有一个动画效果,gif图里可能看不太清,主要是,登录按钮反复掉下弹起,同时用户名密码输入框整体向上移动,类似浮起的效果,这两段动画同时开始同时停止。
(2)用户名密码输入框,在输如数据后有一个清除的小图标,点击小图标,会清空输入框的内容
(3)用户名输入指定的用户名,头像会显示指定的头像,例如我这里效果是输入942845204,头像自动更改为其它头像
(4)用户名输入框最右侧有一个下拉按钮,单机会弹出历史输入信息,点击历史信息条目,自动在用户名密码框中填入对应的信息
(5)登录的加载效果,按钮点击的变色效果,以及声音效果(,,假装可以听到 声音)
当然这些都是完全按照QQ来模仿的。我们按顺序来一个个实现
1.布局的动画效果,这里使用属性动画实现
首先是按钮的掉下弹起效果,使用属性动画ObjectAnimator,直接放代码吧
// 掉下弹起效果 ObjectAnimator yObjectAnimator = ObjectAnimator.ofFloat(btn1, "y", 0, DpUtils.dip2px(QQ.this,260)); yObjectAnimator.setDuration(2200); yObjectAnimator.setInterpolator(new BounceInterpolator()); yObjectAnimator.setRepeatCount(0); yObjectAnimator.start();
这里btn1就是我的登录按钮对象,可以看到核心方法就是ofFloat这个方法,参数解释如下
参数一:要执行动画的Object对象
参数二:执行动画的类型,传入字符串,这个有很多值,例如我这里“y”就表示竖直方向的移动动画,若为“alpha”则为透明度变化的动画,还有一些其他值,就不列举了
参数三和参数四可以看做一组,这里我再这一组里写了两个值,分别代表动画起始y值(即0,表示屏幕最上方),动画结束y值(由于单位是px,这里用工具类转换一下,我这里根据上面控件的高度,设定为260dp)
setDuration方法用于设定动画执行的时间,单位是毫秒,我这里2200代表执行2.2秒
setInterpolator设置执行效果(或者称为加速值),什么意思呢,就是动画在执行的过程中,会有速度上的差异,例如,我可以动画开始时执行速度快,然后慢,然后再快,也可慢-快-慢这样来执行,这里我传入了一个BouncdInterpolartor对象,这个是安卓为我们封装好的一个对象,使用它我们可以直接实现反复掉下弹起的效果。
setRepeatCount设定重复次数,为0,表示只执行一次
最后调用start方法在需要执行的地方调用,开始动画。
2.用户名密码输入框,清除效果
这个效果,现在应该有很多可以直接使用的库了,但是其实自己动手做的话也很简单,自己做的东西有一个好处就是自己想怎么改怎么改,别人的可能因为思路不一样有很多问题,所以自己多动多想,核心代码如下
TextWatcher watcher = new TextWatcher() { @Override public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { String textname = editText1.getText().toString(); if (textname.length() > 0 && editText1.isFocused()) { imageView2.setVisibility(View.VISIBLE); imageView2.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { editText1.setText(""); imageView2.setVisibility(View.INVISIBLE); } }); } else { imageView2.setVisibility(View.INVISIBLE); } if (textname.equals("942845204")) { imageview1.setImageDrawable(getResources().getDrawable(R.drawable.image2)); } else { imageview1.setImageDrawable(getResources().getDrawable(R.drawable.image1)); } String textpassword = editText2.getText().toString(); if (textpassword.length() > 0 && editText2.isFocused()) { imageView3.setVisibility(View.VISIBLE); imageView3.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { editText2.setText(""); imageView3.setVisibility(View.INVISIBLE); } }); } else { imageView3.setVisibility(View.INVISIBLE); } } @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } @Override public void afterTextChanged(Editable arg0) { } }; editText1.addTextChangedListener(watcher); editText2.addTextChangedListener(watcher);
逻辑应该都能看懂,涉及到的就是TextWatcher,这是一个接口,所以我们需要实现它里面的方法,核心方法是onTextChanged,表示设定了该类的EditText在输入内容变化的时候,无论是输入还是删除字符,都会触发该方法,在这里,要实现我们的效果,主要思想就是根据输入框是否有内容来设定旁边叉叉图标的显示与隐藏,然后我们给叉叉图标设置点击事件清空文本框即可达到效果
3.输入指定的用户名,显示对应的头像
这个相信你把第二点看懂了,这个问题就迎刃而解了
4.下拉按钮,弹出窗口,显示历史登录信息
首先,下拉按钮图标,如果你仔细看的话,根据弹出窗口的显示与隐藏,会展示为不同的状态,这里我使用toggleButton实现,当然,你完全可以用普通的button达到效果,不过趁机学习一下toggleButton的使用,何乐而不为呢,嘿嘿嘿
那么怎么实现不同状态显示不同图标的效果呢,代码如下:
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="true" android:drawable="@drawable/qqlog17"/> <item android:state_checked="false" android:drawable="@drawable/qqlog16"/> </selector>
这是一个放在drawable下的xml文件,其中state_checked为状态,true表示打开,false表示关闭,后面对应的drawable设置该状态下,对应显示的图标,可以写一个xml,不过为了方便,我这里就是一张普通的图片啦,然后不要忘了在布局里给toggleButton指定background属性为对上面的drawable对象哦
5.登录的加载效果
这个效果实现起来也比较简单,就是一张gif动态图的显示,我这里使用的也是弹出窗口,在弹出窗口中,显示一个gif动态图,下方一个textview显示加载中的字样,弹出popupwindow对应的布局代码如下
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:id="@+id/linearLayout1" android:layout_width="150dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="@drawable/qq_loging_bg" android:orientation="vertical" > <com.ant.liao.GifView android:id="@+id/gifloging" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="10dp" > </com.ant.liao.GifView> <TextView android:id="@+id/textview1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="10dp" android:text="登录中..." android:textSize="18dp" /> </LinearLayout> </RelativeLayout>
其中com.ant.liao.GifView 标签就是对应的gif图片,这个是一个第三方jar包,很多人都在使用,jar包可以在文章末尾项目源码里找到,在activtiy中的相关代码也很简单,如下
GifView gifloging = (GifView) viewloging.findViewById(R.id.gifloging); gifloging.setGifImage(R.drawable.loging); gifloging.setShowDimension(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
不过这个jar包还是很强大的,我这里只是使用了最简单的功能,有兴趣的可以看看它的其它Api。
除了上面这些小细节,其实也还有很多其它的细节,例如,登录按钮的点击变色效果,我这里还加了一个声音效果,点击有声音的效果也非常简单,先准备一个音频,最好不要太长,不然测试起来会非常,,,,(你懂的)弄个一秒的音频最好,没有的话也可以在我的源码里找到,然后在res目录下,新建一个raw目录,在raw目录下放入你的音频文件即可
播放代码如下,在登录按钮的点击事件里,调用PlayMusic(R.raw.dang)即可,PalyMusic方法如下
private void PlayMusic(int MusicId) { mediaPlayer = MediaPlayer.create(this, MusicId); mediaPlayer.start(); }
以及左下角无法登陆按钮的点击事件,从左下角慢慢弹出一个对话框
这个也是一个动画的运用,弹出框进入动画代码如下
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <scale android:duration="300" android:fromXScale="0" android:fromYScale="0" android:pivotX="0%" android:pivotY="100%" android:toXScale="1.0" android:toYScale="1.0" /> </set>
弹出框退出动画如下
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="300" android:toYDelta="100%p" /> </set>
然后在values下的styles文件下,新添加一个style,代码如下
<style name="mystyle" parent="android:Animation"> <item name="android:windowEnterAnimation">@anim/dialog_enter</item> <item name="android:windowExitAnimation">@anim/dialog_exit</item> </style>
然后创建popupwindow的时候,给popupwindow设置setAnimationStyle(R.style.mystyle)即可
3.注册页面
首先在第一个界面,填写手机号码,左侧有一个按钮可以用来设置手机号码的区号,点击进去是一个listview,然后点击相应的条目,将对应的数据回传给之前的界面并显示,说白了就是两个activity之间如何通信传递数据,由于此处需求非常简单,只需要传递一个String字符串即可,相关传递代码如下
数据发送方activity:
String s = text1.getText().toString() + " "+ text2.getText().toString(); Intent intent= new Intent(QQregister2.this, QQregister.class); intent.putExtra("data_s", s); QQregister2.this.startActivity(intent);
接收方activity:
Intent intent = getIntent(); String data = intent.getStringExtra("data_s"); if (data != null) { button3.setText(data); } else { button3.setText("中国 +86"); }
然后下面还有一个蓝色字体的超链接TextView,这个用到了了一个工具类,用于去除链接文字的下划线,设置链接文字的颜色可以在xml布局的TextView标签下设置textColorLink属性,工具类代码如下
public class URLSpanNoUnderline extends URLSpan { public URLSpanNoUnderline(String url) { super(url); } @Override public void updateDrawState(TextPaint ds) { // TODO Auto-generated method stub super.updateDrawState(ds); ds.setUnderlineText(false); } }
然后在代码里,给TextView设置超链接
textview1 = (TextView) this.findViewById(R.id.textview1); SpannableString st = new SpannableString("我已阅读并同意使用条款和隐私政策"); st.setSpan(new URLSpanNoUnderline("http://zc.qq.com/phone/agreement_chs.html"), 7, 16, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); textview1.setText(st); textview1.setMovementMethod(LinkMovementMethod.getInstance());
然后再用上面的activity之间通信的方式,将选择的手机号码传递到第二个界面,同时生成一段随机验证码发送给第二个界面,来模拟QQ的短信验证码,随机验证码用Random类生成,这里生成六位随机数字,相关代码如下
Random random = new Random(); int[] mes = new int[6]; for (int i = 0; i < 6; i++) { mes[i] = (int) (random.nextInt(9) + 1); }然后就是短信发送的方法了,短信发送用到的类为SmsManager,发送的代码也很简单,如下
SmsManager sm = SmsManager.getDefault(); PendingIntent pi = PendingIntent.getBroadcast(QQregister.this, 0, new Intent(), 0); sm.sendTextMessage(textnumber, null, message, pi, null);
其中sendTextMessage即为发送短信的方法,第三个参数为发送短信的内容,即我们生成的六位随机码。
4.主界面
主界面主要用到的就是Fragment,底部为一个导航栏,点击会在上方显示对应的fragment,这里对初学者来说,有个坑点,就是fragment在安卓中是有两种的,一个是v4包下的,一个是app包下的,所以,在使用的时候,最好统一,不然即便代码没错,但是因为包的问题会导致闪退。关于这两个包的fragment有什么不同,v4包的兼容性更加好,可以兼容到1.6,而app包下的是在3.0以后才有的,所以为了兼容性,一般选择v4下的,二者用法大同小异,下面以动态这个fragment为例,贴一下相关代码
布局如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment3" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="wrap_content" android:divider="#ffffff" android:dividerHeight="0dp" /> </LinearLayout>
对应的fragment代码如下
package com.hq.myqq; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; public class FragmentPage3 extends Fragment { private ListView listview; private String[] str = new String[]{"搜索项", "布局项", "空白标签一", "游戏", "看点", "京东购物", "阅读", "音乐", "直播", "热门活动", "空白便签二", "附近的群", "吃喝玩乐", "同城服务"}; private int[] image = new int[]{R.drawable.rightlisticon1, R.drawable.rightlisticon2, R.drawable.rightlisticon3, R.drawable.rightlisticon4, R.drawable.rightlisticon5, R.drawable.rightlisticon6, R.drawable.rightlisticon7, R.drawable.rightlisticon8, R.drawable.rightlisticon9, R.drawable.rightlisticon10}; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_3, container, false); listview = (ListView) view.findViewById(R.id.listview); BaseAdapter adapter = new BaseAdapter() { @Override public View getView(int arg0, View arg1, ViewGroup arg2) { // TODO Auto-generated method stub LayoutInflater layoutInflater = LayoutInflater.from(getActivity()); View view; if (getItemId(arg0) == 0) { view = layoutInflater.inflate(R.layout.fragment_3_searchview, null); view.findViewById(R.id.buttonsearch).setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { MainTabActivity ma = (MainTabActivity) getActivity(); ma.linesearch(); } }); } else if (getItemId(arg0) == 1) { view = layoutInflater.inflate(R.layout.fragment_3_label, null); view.findViewById(R.id.textView1).setOnClickListener( new OnClickListener() { @SuppressWarnings("static-access") @Override public void onClick(View v) { CustomToast customToast = new CustomToast(getActivity()); customToast.makeText(getActivity(), "好友动态", Toast.LENGTH_SHORT); customToast.setpositionbottom(); customToast.show(); } }); view.findViewById(R.id.textView2).setOnClickListener( new OnClickListener() { @SuppressWarnings("static-access") @Override public void onClick(View v) { CustomToast customToast = new CustomToast(getActivity()); customToast.makeText(getActivity(), "附近", Toast.LENGTH_SHORT); customToast.setpositionbottom(); customToast.show(); } }); view.findViewById(R.id.textView3).setOnClickListener( new OnClickListener() { @SuppressWarnings("static-access") @Override public void onClick(View v) { CustomToast customToast = new CustomToast(getActivity()); customToast.makeText(getActivity(), "兴趣部落", Toast.LENGTH_SHORT); customToast.setpositionbottom(); customToast.show(); } }); } else if (getItemId(arg0) == 2 || getItemId(arg0) == 10) { view = layoutInflater.inflate(R.layout.label, null); } else { view = layoutInflater.inflate(R.layout.fragment_3_groupview, null); ImageView imageView2 = (ImageView) view.findViewById(R.id.imageview2); imageView2.setImageResource(R.drawable.rightlisticon11); ImageView imageView1 = (ImageView) view.findViewById(R.id.imageview1); if (arg0 < 10) { imageView1.setImageResource(image[arg0 - 3]); } else { imageView1.setImageResource(image[arg0 - 4]); } TextView textView = (TextView) view .findViewById(R.id.textview1); textView.setText(getItem(arg0).toString()); } return view; } @Override public long getItemId(int arg0) { return arg0; } @Override public Object getItem(int arg0) { return str[arg0]; } @Override public int getCount() { return str.length; } }; listview.setAdapter(adapter); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); MainTabActivity ma = (MainTabActivity) getActivity(); ma.flashtitledongtai(); listview.setOnItemClickListener(new OnItemClickListener() { @SuppressWarnings("static-access") @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { if (arg2 == 3 || arg2 == 4 || arg2 == 5 || arg2 == 6 || arg2 == 7 || arg2 == 8 || arg2 == 9 || arg2 == 11 || arg2 == 12 || arg2 == 13) { CustomToast customToast = new CustomToast(getActivity()); customToast.makeText(getActivity(), "" + listview.getItemAtPosition(arg2), Toast.LENGTH_SHORT); customToast.setpositionbottom(); customToast.show(); } } }); } }
在这个fragment中,初学者可能会觉得很奇怪,为什么布局里只有一个ListView,最后展现了那么多东西,这是因为在Listview的getView方法中,可以控制,哪一行显示什么内容,通过arg0这个参数,实际上这个参数的含义是代表列表项的位置,从0开始,通过对这个值进行判断,即可实现不同的列表项的效果。
在Activity中控制其显示与隐藏的也就是底部导航栏中的动态按钮,通过设置点击事件,使用FragmentManager和FragmentTransation即可控制fragment的替换。
在主界面,若点击back键,会有弹出对话框的效果,这是一个自定义对话框。
实现原理是创建一个类,继承自Dialog,然后自定义它的布局,以及相关的点击事件,自定义dialog代码如下
package com.hq.myqq; import android.app.Dialog; import android.content.Context; import android.content.res.Resources; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; public class Mydialog extends Dialog implements View.OnClickListener { private TextView textview1; private TextView textview2; private Button button1; private Button button2; static int width = 300; static int height = 150; public Mydialog(Context context, int layout, int style) { this(context, width, height, layout, style); } public Mydialog(Context context, double width, double height, int layout, int style) { super(context, style); setContentView(layout); initWidgets(); // 设置窗口属性 Window window = getWindow(); WindowManager.LayoutParams params = window.getAttributes(); // 设置宽度、高度、密度、对齐方式 float density = getDensity(context); params.width = (int) (width * density); params.height = (int) (height * density); params.gravity = Gravity.CENTER; window.setAttributes(params); } public void setT(String string) { textview1.setText(string); } public void setM(String string) { textview2.setText(string); } public void setButtonLeftText(String string) { button1.setText(string); } public void setButtonRightText(String string) { button2.setText(string); } @Override protected void onStop() { super.onStop(); } private void initWidgets() { button1 = (Button) findViewById(R.id.button1); button1.setOnClickListener(this); button2 = (Button) findViewById(R.id.button2); button2.setOnClickListener(this); textview1 = (TextView) findViewById(R.id.textview1); textview2 = (TextView) findViewById(R.id.textview2); } public float getDensity(Context context) { Resources res = context.getResources(); DisplayMetrics dm = res.getDisplayMetrics(); return dm.density; } @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.button1: if (listener != null) listener.onClickOk(); break; case R.id.button2: if (listener != null) listener.onClickCancel(); break; default: break; } } public void setOnClickBtnListener(OnClickBtnListener listener) { this.listener = listener; } private OnClickBtnListener listener = null; public interface OnClickBtnListener { public void onClickOk(); public void onClickCancel(); } }
然后在使用的时候就只需这样
MyDialog dialog = new Mydialog(MainTabActivity.this,R.layout.dialog_layout, R.style.dialogTheme); dialog.setT("系统提示"); dialog.setM("返回登录界面?"); dialog.setButtonLeftText("确定"); dialog.setButtonRightText("取消"); dialog.setCanceledOnTouchOutside(true); dialog.show(); dialog.setOnClickBtnListener(new Mydialog.OnClickBtnListener() { @Override public void onClickOk() { Intent intent = new Intent(MainTabActivity.this,QQ.class); MainTabActivity.this.startActivity(intent); overridePendingTransition(R.anim.push_right_in,R.anim.push_right_out); } @Override public void onClickCancel() { dialog.cancel(); } });
5.主界面侧拉栏
咳咳,这个侧拉的效果,其实是我在慕课网上找的视频学的,核心思想是继承一个HorizontalScroView,然后重写onMeasure、onLayout、onScrollChanged方法,来达到效果,下面是源代码
public class SlidingMenu extends HorizontalScrollView { private LinearLayout mWapper; private ViewGroup mMenu; private ViewGroup mContent; private int mScreenWidth; private int mMenuWidth; // dp private int mMenuRightPadding = 50; private boolean once; private boolean isOpen; /** * 未使用自定义属性时,调用 * * @param context * @param attrs */ public SlidingMenu(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 当使用了自定义属性时,会调用此构造方法 * * @param context * @param attrs * @param defStyle */ public SlidingMenu(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // 获取我们定义的属性 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyle, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.SlidingMenu_rightPadding: mMenuRightPadding = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 50, context .getResources().getDisplayMetrics())); break; } } a.recycle(); WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels; } public SlidingMenu(Context context) { this(context, null); } /** * 设置子View的宽和高 设置自己的宽和高 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!once) { mWapper = (LinearLayout) getChildAt(0); mMenu = (ViewGroup) mWapper.getChildAt(0); mContent = (ViewGroup) mWapper.getChildAt(1); mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding; mContent.getLayoutParams().width = mScreenWidth; once = true; } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 通过设置偏移量,将menu隐藏 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed) { this.scrollTo(mMenuWidth, 0); } } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_UP: // 隐藏在左边的宽度 int scrollX = getScrollX(); if (scrollX >= mMenuWidth / 2) { this.smoothScrollTo(mMenuWidth, 0); isOpen = false; } else { this.smoothScrollTo(0, 0); isOpen = true; } return true; } return super.onTouchEvent(ev); } /** * 打开菜单 */ public void openMenu() { if (isOpen) return; this.smoothScrollTo(0, 0); isOpen = true; } public void closeMenu() { if (!isOpen) return; this.smoothScrollTo(mMenuWidth, 0); isOpen = false; } /** * 自己添加的 判断左侧菜单是否打开 */ public boolean open() { if (isOpen) { return true; } return false; } /** * 切换菜单 */ public void toggle() { if (isOpen) { closeMenu(); } else { openMenu(); } } /** * 滚动发生时 */ @Override public void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); float scale = l * 1.0f / mMenuWidth; // 1 ~ 0 /** * 区别1:内容区域1.0~0.7 缩放的效果 scale : 1.0~0.0 0.7 + 0.3 * scale * * 区别2:菜单的偏移量需要修改 * * 区别3:菜单的显示时有缩放以及透明度变化 缩放:0.7 ~1.0 1.0 - scale * 0.3 透明度 0.6 ~ 1.0 0.6+ * 0.4 * (1- scale) ; * */ float rightScale = 0.7f + 0.3f * scale; float leftScale = 1.0f - scale * 0.3f; float leftAlpha = 0.6f + 0.4f * (1 - scale); // 调用属性动画,设置TranslationX ViewHelper.setTranslationX(mMenu, mMenuWidth * scale * 0.8f); ViewHelper.setScaleX(mMenu, leftScale); ViewHelper.setScaleY(mMenu, leftScale); ViewHelper.setAlpha(mMenu, leftAlpha); // 设置content的缩放的中心点 ViewHelper.setPivotX(mContent, 0); ViewHelper.setPivotY(mContent, mContent.getHeight() / 2); ViewHelper.setScaleX(mContent, rightScale); ViewHelper.setScaleY(mContent, rightScale); } }
然后在xml布局里直接以标签的形式就可以使用。
6.消息电话按钮切换效果
这个其实就是对drawable这个东西的使用需要比较熟练了,实现原理是,给左右两边两个按钮分别设置两个背景,要注意,给按钮设置圆角时,例如左边按钮,圆角只有左上和坐下两个。想关代码如下
左边按钮的两个drawable代码
选中时
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <!-- 填充的颜色:这里设置背景透明 --> <solid android:color="#ff304a" /> <!-- 边框的颜色 :不能和窗口背景色一样 --> <stroke android:width="2dp" android:color="#ff304a" /> <!-- 设置按钮的四个角为弧形 --> <!-- android:radius 弧形的半径 --> <corners android:bottomLeftRadius="5dip" android:bottomRightRadius="0dip" android:topLeftRadius="5dip" android:topRightRadius="0dip" /> <!-- padding:Button里面的文字与Button边界的间隔 --> <padding android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp" /> </shape>非选中时
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <!-- 填充的颜色:这里设置背景透明 --> <solid android:color="#00000000" /> <!-- 边框的颜色 :不能和窗口背景色一样 --> <stroke android:width="2dp" android:color="#ff304a" /> <!-- 设置按钮的四个角为弧形 --> <!-- android:radius 弧形的半径 --> <corners android:bottomLeftRadius="5dip" android:bottomRightRadius="0dip" android:topLeftRadius="5dip" android:topRightRadius="0dip" /> <!-- padding:Button里面的文字与Button边界的间隔 --> <padding android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp" /> </shape>右边的同理,就不赘述啦,然后在代码中,相应按钮的点击事件设置相应的背景色即可
7.自定义Toast
原理也是和自定义dialog一样,创建一个类,继承Toast,下面是代码
public class CustomToast extends Toast { static Toast toast; public CustomToast(Context context) { super(context); } public static Toast makeText(Context context, CharSequence text, int duration) { toast = new Toast(context); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.toast_layout, null); TextView textView = (TextView) view.findViewById(R.id.textview1); textView.setText(text); toast.setView(view); toast.setGravity(Gravity.TOP, 0, 30); toast.setDuration(duration); return toast; } public void show() { toast.show(); } public static void setpositioncenter() { toast.setGravity(Gravity.CENTER, 0, 50); } public static void setpositionbottom() { toast.setGravity(Gravity.BOTTOM, 0, 50); } }
8.特殊图片.9.png图片的用法
为什么要讲一下这个呢,因为在某些特殊场景里,可能会遇到图片大小,会根据呈现的文字而自动拉伸,如果你不能理解的话,相信你应该用过QQ,在QQ发送消息的时候,无论你的消息内容有多长,最后背景图片是不是都是正常的,丝毫看不出来有任何的变形,那么这种场景下,是无法用普通图片来实现的,那么就需要用到.9图了,在这个例子中,有一个地方也需要用到.9图,哪里呢,见下图
在这个弹出窗口中,你可看到背景图是一个消息一样的背景图,如果窗口里面的内容增加了一条或者减少了一条,那么如果用固定图片,就会产生拉伸,很丑也很不友好,.9图的作用就来了,.9图的一个特殊地方就是可以为它设置内容区,那么将这个图片作为内容区得时候,拉伸就只会拉伸内容区,其他区域不会变化,那么如何制作.9图呢,看下面
选中任意一张图片,右键,选择Create-9-Patch file便可进入.9的制作界面,如下
在右侧你可以预览图片横向拉伸,和纵向拉伸后的效果,而制作过程也很简单,按住shift键,在图片边缘画上内容区代表的宽度和高度,也就是图中的黑线,两条黑线的交叉区域就是内容区了,如下
然后再拉伸的时候,内容区水平方向和竖直方向均会拉伸,四个角落,没有被阴影触及到的地方,两个方向均不会拉伸
内容区上下两个阴影,是会水平方向拉伸,左右两个阴影会竖直方向拉伸。
有了这样一张图片,我们就不怕内容区的更改而造成图片变形的问题啦!
结语
由于内容很多,加上细节也很多,例如ExpandListView的指示器、底部导航栏的图标点击效果等,这里主要挑了一些重要的列出来,当时学习的时候,觉得写完这个QQ后,对我是十分有帮助的,也希望这篇文章能帮助到更多的人。当然,在实际实现的时候,如果遇到了问题可以去直接看我的源码,也可给我留言,我都会及时回复。