仿微信随手指滑动页面菜单图标文字颜色渐变

微信大家肯定都用过,进入微信首页,下面有四个菜单,用户用手指滑动页面的时候,会发现下面的菜单图标还有文字的颜色会出现渐变,当然也可以直接用手点击某个菜单,不过这样就看不到渐变的效果,今天主要来说明下随手指滑动页面的菜单文字图标渐变效果的实现,先看效果图:



下面先说明一下设计思路,界面分为上下两个部分,上面的部分是一个ViewPager,它有4个界面,ViewPager随着手势滑动可以在界面中进行切换,而下面的部分是一排图标和文字,随着手势的滑动,图标和文字的颜色要逐渐变化,要实现这一点,首先想到的肯定是用ViewPager的OnPageChangeListener,它里面提供了3个方法来监测ViewPager内页面滑动的状态,而图标和文字的颜色渐变看着好像是一个图标和一个文字变化,其实图标和文字都是2个ImageView和2个TextView的重叠,通过监听滑动来设置它们的透明度,下面来具体说明:


第一步:布局的实现

页面的上半部是一个ViewPager

<android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </android.support.v4.view.ViewPager>

这没什么好说的,就是定义一个ViewPager

页面的下半部是一排按钮,为了简化问题,我们只看其中一个

<LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:orientation="vertical" 
                android:onClick="wechatClick">

                <RelativeLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" >

                    <ImageView
                        android:id="@+id/wechat_icon_select"
                        android:layout_width="40dp"
                        android:layout_height="40dp"
                        android:background="@drawable/wechat_select" />

                    <ImageView
                        android:id="@+id/wechat_icon_normal"
                        android:layout_width="40dp"
                        android:layout_height="40dp"
                        android:background="@drawable/wechat_normal" />
                </RelativeLayout>

                <RelativeLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" >

                    <TextView
                        android:id="@+id/wechat_text_select"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="微信" 
                        android:textColor="#51C650"
                        />

                    <TextView
                        android:id="@+id/wechat_text_normal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="微信" 
                        android:textColor="#999999"
                        />
                </RelativeLayout>
            </LinearLayout>

这是一个垂直方向的布局,上下各一个RelativeLayout,每个RelativeLayout里面分别是两个ImageView和两个TextView,也就是图标和文字,因为放在RelativeLayout里面,所以是重叠的,我们稍后将通过设置Alpha来改变它们的透明度。

第二步:定义ViewPager控件

在MainActivity.java中,定义ViewPager控件以及其中的布局

public class MainActivity extends Activity {
	private View wechatView, contactView, findView, meView;
	private ViewPager viewPager;
	private List<View> viewList; // 布局集合
	private LayoutInflater mInflater;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		initView();
	}

	private void initView() {
		mInflater = LayoutInflater.from(this);
		viewList = new ArrayList<View>();
		viewPager = (ViewPager) findViewById(R.id.viewpager);

		wechatView = mInflater.inflate(R.layout.wechat_layout, null);
		contactView = mInflater.inflate(R.layout.contact_layout, null);
		findView = mInflater.inflate(R.layout.find_layout, null);
		meView = mInflater.inflate(R.layout.me_layout, null);

		viewList.add(wechatView);
		viewList.add(contactView);
		viewList.add(findView);
		viewList.add(meView);

		viewPager.setAdapter(new MyPagerAdapter(viewList));
	}
}
这里比较简单,就是定义ViewPager控件,完后将4个页面布局通过Adapter设置进ViewPager,来填充ViewPager的内容,看下 MyPagerAdapter的实现

public class MyPagerAdapter extends PagerAdapter {
	private List<View> mListViews;

	public MyPagerAdapter(List<View> mListViews) {
		this.mListViews = mListViews;
	}

	@Override
	public void destroyItem(ViewGroup container, int position, Object object) {
		container.removeView(mListViews.get(position));
	}

	@Override
	public Object instantiateItem(ViewGroup container, int position) { 
		container.addView(mListViews.get(position), 0);
		return mListViews.get(position);
	}

	@Override
	public int getCount() {
		return mListViews.size();
	}

	@Override
	public boolean isViewFromObject(View arg0, Object arg1) {
		return arg0 == arg1;
	}
}

这就是标准的自定义 PagerAdapter,实现了它的一些基础方法,这里我们把布局的List通过构造函数传进来,最后看下布局的实现,4个布局都是差不多的,以 wechat_layout为例:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical" >
    
	<TextView
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:text="微信"
	    />
</LinearLayout>

可以看到,就是页面中间一个TextView,用来标明滑动到哪一页了

第三步:监听ViewPager

这里就是实现的重点了,首先来看下OnPageChangeListener的onPageScrolled方法

public abstract void onPageScrolled (int position, float positionOffset, int positionOffsetPixels)

This method will be invoked when the current page is scrolled, either as part of a programmatically initiated smooth scroll or a user initiated touch scroll.

Parameters
position Position index of the first page currently being displayed. Page position+1 will be visible if positionOffset is nonzero.
positionOffset Value from [0, 1) indicating the offset from the page at position.
positionOffsetPixels Value in pixels indicating the offset from position.
这是官方的说明,说得挺简单的,三个参数,第一个参数position就是当前展示的页面,第二个参数是页面滑动距离的比例,值为大于等于0,小于1,第三个参数是滑动的距离

但实际使用中,发现和想象的不太一样,对于position参数

如果手指向左拖动(相应页面向右翻动),这时候position大部分时间和当前页面是一致的,只有翻页成功的情况下最后一次调用才会变为目标页面;如果手指向右拖动(相应页面向左翻动),这时候position大部分时间和目标页面是一致的,只有翻页不成功的情况下最后一次调用才会变为原页面。

有点绕口,举例说明,从第0页翻转到第1页,position始终是0,直到最后页面停止,最后变成1,而从第1页翻转到第0页,position的值始终是0,除非你翻到中途又回来了,它才会在最后停止时变为1,其实这个逻辑设计的有点奇怪,因为左翻和右翻的逻辑不对称,不过正是这样才极大的简化了我们的逻辑判断,后面可以看到

再来看看这个positionOffset参数,这个参数和谷歌官方的描述是一致的,不过还是有一点要注意,比如从第0页翻到第1页,它的值一开始并不是0,而是某个很小的值,完后随着翻动距离的变大而不断增加,最后趋近于1,但到了最后结束时,它会变为0,这点要注意

理解了这些参数的含义,我们来看具体实现:

@Override
		public void onPageScrolled(int index, float ratio, int offset) {
			if (ratio > 0) {
				colorChange(index, index + 1, ratio);
			}
		}

这里调用了一个自定义的方法,继续看:

/**
	 * 
	 * @param srcIndex 失去焦点的索引
	 * @param destIndex 选中的索引
	 * @param ratio 透明的比例
	 */
	private void colorChange(int srcIndex, int destIndex, float ratio) {
		iconList.get(srcIndex * 2).setAlpha(ratio);
		iconList.get(srcIndex * 2 + 1).setAlpha(1 - ratio);

		iconList.get(destIndex * 2).setAlpha(1 - ratio);
		iconList.get(destIndex * 2 + 1).setAlpha(ratio);

		textList.get(srcIndex * 2).setAlpha(ratio);
		textList.get(srcIndex * 2 + 1).setAlpha(1 - ratio);

		textList.get(destIndex * 2).setAlpha(1 - ratio);
		textList.get(destIndex * 2 + 1).setAlpha(ratio);
	}

 这就是我们实现的核心地方,这里的 
iconList是一个数组,用来存放图标,我们的界面中总共有4个菜单,但我一开始就说了,每个菜单的图标其实是2个ImageView重叠的,所以这个数组中实际存放了4*2共8个ImageView,2个为1组存放,共4组,每组都是先存放未选中的图标(灰色),再存放选中的图标(绿色),我们以从0页翻到1页为例来进行说明: 

对于第0页的图标,也就是上面的“微信”图标,它需要做两件事,未选中图标逐渐颜色变深,直到完全显现,而选中图标逐渐颜色变浅,直到完全消失,也就是上面代码中的:

iconList.get(srcIndex * 2).setAlpha(ratio);
		iconList.get(srcIndex * 2 + 1).setAlpha(1 - ratio);


 

而对于第1页的图标,也就是上面的“通讯录”图标,它也需要做两件事,未选中图标逐渐颜色变浅,直到完全消失,而选中图标逐渐颜色变深,直到完全显现,也就是:

iconList.get(destIndex * 2).setAlpha(1 - ratio);
		iconList.get(destIndex * 2 + 1).setAlpha(ratio);


下面的四行是对textList的操作,也就是文字,这个和图标是一致的,只不过从ImageView变成了TextView而已,不再重复说明。

上面是从0页到第1页,其实从第1页到第0页也是一样的,对照上面的代码和参数的理解,同样的道理,这里要感谢谷歌的设计了,虽然感觉有点怪,但这样的设计避免我们判断是左滑还是右滑,省了不少事。

另外我们还要注意,在onPageScrolled里面判断了ratio是否大于0,为什么要加这个判断呢,参考上面对positionOffset参数的解释,从0页翻到1页,它最后一刻会变为0,如果不加这个判断,会导致滑动到最后,又变为第0个图标被选中了。

最后需要注意的一点是,由于positionOffset始终没有成为1,所以严格意义上来说,选中的图标的透明度也不会到1,也就是选中的图标并不会完全显示,但是临近最后的时刻,positionOffset的值是比较趋近于1的,所以这点误差也就忽略不计了,肉眼看不出来(除非用非常快的速度滑动,使得positionOffset的值跨度很大,最终没有趋近于1,而是从0.8之类的值直接变为0,这种情况概率很低,而且对微信测试也会低概率的出现问题,所以不考虑这种极端情况)


第四步:定义点击事件

在第一步的布局中,定义了点击事件android:onClick="wechatClick",也就是点击“微信”菜单后的响应

public void wechatClick(View view) {
		if (curIndex != WECHAT_INDEX) {
			colorChange(WECHAT_INDEX, curIndex, 0);
			
			viewPager.setCurrentItem(WECHAT_INDEX, false);
		}
	}

这里的 WECHAT_INDEX是自定义的变量,也就是菜单的索引值,curIndex是记录的当前菜单索引,在onPageSelected中赋值

@Override
		public void onPageSelected(int index) {
			curIndex = index;
		}
注意这个方法也是OnPageChangeListener里面的,它的值也很有意思,它在页面滑动的过程中就变化了,比如从0页翻到1页,它在翻动过程中就变为1了,而不是等到翻动结束才变化,不过这里是取得最终的值,所以是翻动过程中还是翻动结束时变化没有影响。

点击菜单时,首先判断是否是当前菜单,如果是就不用处理了,如果不是,则调用第三步中的colorChange方法改变图标和文字即可,最后调用setCurrentItem方法来修改ViewPager的显示页,注意点击事件不是渐变的,是直接变化一步到位的,所以这里参数传递false,表示不平滑过渡,如果传递true,则表示平滑过渡,会导致多次出发colorChange方法,这不是我们想要的结果。


总结:这里主要关注ViewPager,所以没有对界面进行优化,导致所有控件都定义在布局中,布局比较臃肿,有时间的可以自己重构,通过自定义控件来定义一个菜单项,可以简化代码。


源码下载







  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值