转载请注明出处:http://blog.csdn.net/llew2011/article/details/51559694
好久没有写有关UI的博客了,刚刚翻了一下之前的博客,最近一篇有关UI的博客是在2014年写的:Android UI设计之<七>自定义Dialog,实现各种风格效果的对话框,在那篇博客写完后由于公司封闭开发封网以及其它原因致使博客中断至今,中断这么久很是惭愧,后续我会尽量把该写的都补充出来。近来项目有个需求,要做个和QQ空间类似的菜单栏透明度渐变和下拉刷新带有阻尼回弹的效果。于是花点时间动手试了试,基本上达到了QQ空间的效果,截图如下:
通过观察QQ空间的运行效果,发现当往上滚动时菜单栏会随着滚动距离的增大其透明度组件增大直到完全不透明,反之逐渐透明。当滚动到顶部后继续下拉会出现拉升效果当松手之后出现阻尼回弹效果。于是就通过重写ListView模仿了QQ空间的运行效果。
实现QQ空间运行效果前需要考虑两个问题:
- 如何实现菜单栏透明度渐变
通过观察QQ空间的运行效果可知其菜单栏的透明度是根据滚动距离而动态变化的,要想实现透明度的变化就需要知道的ListView的滚动距离,所以有关透明度的问题也就转化成了滚动距离的问题。 - 如何实现阻尼拉升和回弹效果
要想利用ListView实现阻尼效果就要求ListView首先滚动到了顶部,当ListView滚动到了顶部之后若继续手动下滑就要求其第一个Child变化来模拟下拉效果,当手指松开后该Child要回弹到初始状态。
我们先看第一个问题:要想实现透明度渐变就要先获取到ListView的滚动距离,通过滚动距离来计算相应的透明度。由于ListView的复用机制就决定了不能通过第一个可见Item的getTop()方法来得到滚动值,所以我们可以通过HeaderView来获取滚动距离,因为Header在ListView中是不参与复用的。
下面先了解一下ListView添加HeaderView后的滚动流程:
上图大致画了ListView含有HeaderView时的三个滚动状态,状态一可称为初始状态或者是恰好滚动到最顶部状态,此时HeaderView的getTop()值为0;状态二为ListView的滚动中状态,此时HeaderView没有完全滚动出ListView边界,getTop()的返回值为负数且其绝对值范围在0和HeaderView的高度之间;状态三表示的是HeaderView完全滚动出了ListView边界,若调用getTop()得到的返回值为负数且绝对值等于HeaderView的高度(此后可理解成HeaderView一直固定在ListView的顶部)。
明白了ListView的滚动原理,我们先尝试实现渐变菜单栏的功能。首先定义自己的ListView,取名为FlexibleListView,单词flexible是灵活的、多样的的意思,因为我们的ListView不仅要实现菜单栏的透明度渐变还要实现阻尼效果,所以取名为FlexibleListView比较恰当。FlexibleListView继承ListView后需要实现其构造方法,代码如下:
public class FlexibleListView extends ListView {
public FlexibleListView(Context context) {
super(context);
}
public FlexibleListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
FlexibleListView仅仅是继承了ListView,这本质上和ListView没有区别。既然我们是通过给ListView添加HeaderView的方式来判断滚动距离,那就要获取到HeaderView对象。怎么获取到HeaderView对象呢?这里有个技巧,由于给ListView添加HeaderView最终是调用ListView的addHeaderView(View v, Object data, boolean isSelected)方法,所以我们可以重写该方法,取到添加进来的第一个HeaderView,那怎么判断是第一个添加进来的HeaderView呢?因为HeaderView的添加是有序的即先添加的先绘制。所以可以定义一个代表第一个HeaderView的属性mHeaderView,当调用到addHeaderView()方法时通过判断mHeaderView的值是否为空,如果为空就赋值否则不赋值,代码如下:
public class FlexibleListView extends ListView {
private View mHeaderView;
private int mMaxScrollHeight;
public FlexibleListView(Context context) {
super(context);
}
public FlexibleListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void addHeaderView(View v, Object data, boolean isSelectable) {
super.addHeaderView(v, data, isSelectable);
if(null == mHeaderView) {
mHeaderView = v;
mMaxScrollHeight = mHeaderView.getLayoutParams().height;
}
}
}
FlexibleListView中定义了mHeaderView和mMaxScrollHeight属性,在addHeaderView()方法中对mHeaderView做非空判断来获取到第一个HeaderView并赋值给mHeadereView,mMaxScrollHeight表示HeaderView的最大滚动距离,当HeaderView的滚动距离超过此值我们就要设置菜单栏不透明否则就更改透明度。在这里我直接使用了HeaderView的高度来表示其允许滚动的最大距离。
现在可以获取到ListView的第一个HeaderView,接下来就是判断ListView的滚动了,这时候有的童靴可能会想到采用给ListView添加ScrollListener的方式,这种方式是可行的,但我们这次不采用添加Listener的方