实现这个功能主要涉及的知识点有
- 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指针调用有两个参数的构造方法,同理,两个的调用三个的。
关于自定义组件的知识可以参考另外两篇博客
该功能目前应用于我的XMPP项目
https://github.com/Leaking/xmppIM
后续坚持把写这个开源项目使用到的知识都记录在博客里。