实现这个功能主要涉及的知识点有
- ViewPager
- PorterDuffXfermode
- 自定义视图
ViewPager
关于ViewPager需要注意的知识主要是OnPageChangeListener,该接口的三个方法如下
public abstract void onPageScrollStateChanged (int state) public abstract void onPageScrolled (int position, float positionOffset, int positionOffsetPixels) public abstract void onPageSelected (int position)
第一个方法onPageScrollStateChanged 中的参数state,有三个可取的值
public static final int SCROLL_STATE_DRAGGING Constant Value: 1 (0x00000001) public static final int SCROLL_STATE_IDLE Constant Value: 0 (0x00000000) public static final int SCROLL_STATE_SETTLING Constant Value: 2 (0x00000002)
SCROLL_STATE_DRAGGING:手指按在viewpager上滑动时
SCROLL_STATE_SETTLING:手指松开后,viewpager自动滑动期间
SCROLL_STATE_IDLE:viewpager滑动进入了某个page。
如果你在onPageScrollStateChanged 中输出state的值,你会发现每次都是按顺序打印出“1---2----0”
第二个方法onPageScrolled的三个参数
position:滑动时,屏幕左侧显示的第一个page
positionOffset:滑动比例,值的范围为[0, 1),手指往左滑动,该值递增,反之递减
positionOffsetPixels:滑动距离,和屏幕有关,手指往左滑动,该值递增,反之递减
我们经常需要检查viewpager的滑动方向并作出一些操作,这时你只需要通过position和positionOffset两个值即可实现该功能。
第三个方法onPageSelected的三个参数
position:当前选择的page序号
该方法被调用的时间比较特别,在上面的第一个方法中的“1---2----0”中的2执行之后,onPageSelected就执行,然后执行“1---2----0”中的0。就是手指松开屏幕之后,onPageSelected被执行。
以下在代码中自己重写的一个OnPageChangeListener
/** * @author Quinn * @date 2015-2-28 */ public class MainPagerChangeListener implements OnPageChangeListener{ public interface PagerCallback{ public void changePageColor(int index, int offset); } private PagerCallback callback; public MainPagerChangeListener(MainActivity mainActivity){ this.callback = (PagerCallback)mainActivity; } //顺序为1-2-0 @Override public void onPageScrollStateChanged(int state) { } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if(positionOffset != 0.0f){ callback.changePageColor(position+1, (int)(positionOffset * 255)); callback.changePageColor(position, (int)((1-positionOffset) * 255)); } } @Override public void onPageSelected(int position) { } }
首先我们留一个回调接口PagerCallback,然后在onPageScrolled中执行操作。可以根据position和positionOffset两个值,对两个方向的page进行不同的操作。我们这里进行的操作是改变图标的颜色。
PorterDuffXfermode
先看以下这幅图
微信的底部图案,其原型是一个灰色的图案,在滑动时,会发生颜色渐变,那么怎样使一张图片产生这样的效果呢?其原理是利用PorterDuffXfermode!先看以下这张图片
上述16个图,我们看第二排的第三个,黄色部分代表Dst,蓝色部分代表Src,将Dst覆盖在Src之上,选择"DstIn"模式,就可以合成上述第二排第三张图片。那么,如果Src是一个灰色的图片(图片部分是透明的,像上面微信的四个图标),Dst是一个绿色的矩形,其大小和图片一致。然后将绿色的矩形覆盖在图片之上,选择“DstIn”模式,就能达到将图片设置成绿色的效果。在这个基础上,要对图片进行颜色渐变,只需在viewpager滑动时修改上述Dst矩形的颜色透明度即可。实现代码可以在文章末尾下载源码。博客主要记录实现思路和细节,贴出代码感觉太冗长了。
自定义视图
自定义视图,一般按以下步骤进行
- 自定义属性
- 获取属性
- 重写onMeasure
- 重写onDraw
我们在自定义底部带文字的图标,自定义属性如下:
路径:XMPP\res\values\attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="FooterTextIcon"> <attr name="iconSrc" format="reference" /> <attr name="color" format="color"/> <attr name="text" format="string"/> <attr name="textSize" format="dimension"/> </declare-styleable> </resources>
这些自定义属性的使用方式如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:FooterIcon="http://schemas.android.com/apk/res/com.quinn.xmpp" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.quinn.xmpp.ui.main.MainActivity" > //省略,,,, //,,, //,,, <com.quinn.xmpp.ui.widget.FooterTextIcon android:id="@+id/chattingIcon" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" FooterIcon:iconSrc="@drawable/ic_action_chat" FooterIcon:text="聊天" FooterIcon:textSize="12sp" android:padding="5dip" FooterIcon:color="#ff181818" /> //省略,,,, //,,, //,,, </LinearLayout>
在使用自定义属性时,需要在使用这些自定义属性的布局文件中引入一个命名空间,比如上述代码的第三行,它的格式是:
xmlns:自己起一个名字="http://schemas.android.com/apk/res/应用的包名"
注意,上面代码的最后一部分是应用的包名,而不是某个Activity的包名或者自定义组件对应Java文件的包名
前面自定义属性的attrs.xml中,可以自定义的属性类型(对应上述的format)有以下几种
reference、string、color、dimension、boolean、integer、float、fraction、enum、flag
主要讲讲其中比较常用而且容易混淆的四个
reference的值必须是"@drawable/xxxxxx"
比如上面的
FooterIcon:iconSrc="@drawable/ic_action_chat"
而string,color,dimension的值可以直接写某个值,也可以是引用于资源文件string.xml,color.xml,dimens.xml
比如
FooterIcon:text="聊天" FooterIcon:textSize="12sp" android:padding="5dip" FooterIcon:color="#ff181818"</span>
也可以写成
FooterIcon:text="@string/xxxxx" FooterIcon:textSize="@dimen/xxxx" android:padding="@dimen/xxxx" FooterIcon:color="@color/xxxx" />
然后,我们需要在一个地方使用这些属性的值,就是在我们的自定义组件的类文件中。如下:
public class FooterTextIcon extends View { //四个属性 private String text; private int themeColor; private int textSize; private int iconRid; //、、、 /** * @param context */ public FooterTextIcon(Context context) { this(context, null); } public FooterTextIcon(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * @param context * @param attrs * @param defStyleAttr */ public FooterTextIcon(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FooterTextIcon); iconRid = a.getResourceId(R.styleable.FooterTextIcon_iconSrc, -1); text = a.getString(R.styleable.FooterTextIcon_text); textSize = (int) a.getDimension(R.styleable.FooterTextIcon_textSize, TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics())); themeColor = a.getColor(R.styleable.FooterTextIcon_color, Color.parseColor(DEFAULT_CHOSEN_COLOR)); a.recycle(); //,,, } //,,省略,, }
如上,我们在构造函数中,通过一个TypeArray对象获取各种类型的属性,并且最后调用TypeArray的recycle函数回收内存。一般我们将这些代码放在有三个参数的构造方法中执行,而又一个参数的构造方法直接用this指针调用有两个参数的构造方法,同理,两个的调用三个的。
关于自定义组件的知识可以参考另外两篇博客
android自定义View-------为什么重写onMeasure()以及怎么重写
该功能目前应用于我的XMPP项目 https://github.com/Leaking/xmppIM
后续坚持把写这个开源项目使用到的知识都记录在博客里。