这次UI又脑洞大开,嫌弃我们项目中的 SwipeRefreshLayout 的下拉效果没特色,整了一个带主题色彩的下拉刷新,效果图如下:
想到又要手撸一个下拉刷新,心里顿时上万条草泥马奔腾。做为一只傲娇程序猿,我们怎么能干出这种吃力不讨好的事呢(逻辑判断、Touch事件分发、状态变化写起来很麻烦,还特么可能有一大堆八阿哥)。吃口屎冷静了一下,先分析吧~
要实现这个下拉刷新,主要的难点有:
1、下拉刷新功能的实现
2、刷新过程中的 UI 动效
3、 Demo 写出来了,要是一个一个页面去移植这个下拉刷新,那又得花不少时间,怎样更快捷的方式替换掉项目中几十上百个页面。
解决思路:
难点一:说的下拉刷新的功能实现,很多小伙伴都会说PullToRefreshListview、SwipeRefreshLayout呀,度娘一搜一大把。对,没错,我也是直接在 SwipeRefreshLayout 这个类上修改了刷新 Ui 效果。至于为什么不用PullToRefreshListview,因为我们项目中还有很多页面并不是列表页,但是也需要刷新。
难点二:刷新过程中的 UI 动效,这个后文我会带着大家分析代码实现。
难点三:全局搜索替换,把“android.support.v4.widget.SwipeRefreshLayout”替换成你的控件名就好,注意接口回调以及方法名和SwipeRefreshLayout保持一致即可。
好了,问题都已经分析完了,接下来就撸代码吧~
难点一解决:我的解决方案是直接修改了SwipeRefreshLayout,但是由于公司的源码不方便拿出来讲解,这里我在 Github 上随便搜了一个和SwipeRefreshLayout 功能一样的 Github Demo 来作讲解,最终实现效果不好有任何影响,希望大家不要介怀。
使用起来很简单
在 需要刷新的 View 父节点用 RefreshLayout 包裹
<com.rongyi.diamond.pulltorefresh.RefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#FFDAB9"
android:gravity="center"
android:text="Target"
android:textSize="30sp"/>
</com.rongyi.diamond.pulltorefresh.RefreshLayout>
然后在 Java代码中设置自定义的下拉刷新头即可。
RefreshLayout refreshLayout=(RefreshLayout)findViewById(R.id.refreshLayout);
ShopView shopView=new ShopView(this)
refreshLayout.setRefreshHeader(shopView);
其中RefreshLayout就是GitHub 上面搜索的和SwipeRefreshLayout相似的类,ShopView 就是我自己写的自定义下拉刷新样式。
RefreshLayout 功能:拦截Touch 事件根据判断子 View 是否滑动到顶部来判断是否要显示刷新头,就是一个类似于RefreshLayout的容器。具体实现这里就不做重点讲解了,有兴趣的小伙伴自己自己下载下来读一遍,源码我会在文章结尾处贴出来。
ShopView:自定义的请求头,难点二中会详细讲解ShopView的实现。
难点二解决:
第一步:新建一个 ShopView 类,继承RelativeLayout(继承 ViewGroup 也行,我嫌弃麻烦),然后在构造方面里面 View.inflate一个布局到当前 View。
第二步:让布局里面的元素在合适的时候动起来
首先我们来分析刷新头。动画中的基本元素有:1.购物袋上的手提绳、2.“直击全国专柜特卖现场”文字图片、3.购物袋中喷出的商品、4.购物袋、
观察 UI 效果图我们发现,购物袋需要在刷新的时候抖动、购物袋上的手提绳需要在下拉的过程中改变效果、刷新的时候购物袋中会喷出商品、刷新头完整出现之后继续下拉会出现“直击全国专柜特卖现场”。
由于这些元素都包含在刷新头里面,但是上面的4种动画元素是需要根据不同的状态来展示的,因此,我们需要一个接口来监听RefreshLayout中的状态变化,这里原作者已经提供了状态回调,我们直接用就行了。
public interface RefreshHeader{
/**
* 松手,头部隐藏后会回调这个方法
*/
voidreset();
/**
* 下拉出头部的一瞬间调用
*/
voidpull();
/**
* 正在刷新的时候调用
*/
voidrefreshing();
/**
* 头部滚动的时候持续调用
*
* @paramcurrentPostarget当前偏移高度
* @paramlastPostarget上一次的偏移高度
* @paramrefreshPos可以松手刷新的高度
* @paramisTouch手指是否按下状态(通过scroll自动滚动时需要判断)
* @paramstate当前状态
*/
voidonPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate);
/**
* 刷新成功的时候调用
*/
voidcomplete();
}
直接让我们的 ShopView 实现这个接口,在相应的状态回调里面展示相应的动画即可。
接下来就只需要把这四个动画撸出来就完成我们的定制下拉刷新了~
动画一:手提绳的变化
首先,在onPositionChange()条目下拉的时候改变手提绳的效果。经过观察我们发现手提袋就是一个上下翻滚的效果,这里我们直接用一个二阶贝塞尔曲线,起始点不动,控制点 x 轴在起始点正中间,y 轴根据onPositionChange()变化而变化即可。实现核心代码如下:
@Override
public void onPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate){
mBezierLine.setControlY(currentPos);
}
这里直接把手提袋封装了一个 View,setControlY方法实际上就是改变了控制点的 Y 轴,重新绘制了 View
public void setControlY(floaty){
if(isRefresh){
return;
}
if(y+min
control.y=y+min;
}else if(y+min>max&&y+min<2*(max-min)){
control.y=2*max-min-y;
}else{
control.y=min;
}
invalidate();
}
绘制代码如下:
protected voidonDraw(Canvascanvas){
super.onDraw(canvas);
// 绘制贝塞尔曲线
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(2);
Pathpath=newPath();
path.moveTo(start.x,start.y);
path.quadTo(control.x,control.y,end.x,end.y);
canvas.drawPath(path,mPaint);
}
动画二:当条目下拉过程中超过 刷新头的高度时,改变“直击全国专柜特卖现场”的setTranslationY即可
@Override
public void onPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate){
if(currentPos>mHeight){
inttranslationY=(int) (currentPos-mHeight);
intdp20=Utils.dp2px(getContext(),20);
if(translationY>dp20){
translationY=dp20;
}
mIvTrans.setTranslationY(dp20-translationY);
}else{
mIvTrans.setTranslationY(Utils.dp2px(getContext(),20));
}
}
动画三:在刷新的回调中开启喷出商品的动画,在刷新结束的时候关闭即可
@Override
public voidrefreshing(){
star();
}
Handlerhandler=newHandler(){
@Override
public void handleMessage(Messagemsg){
super.handleMessage(msg);
addHeart();
handler.sendEmptyMessageDelayed(0,250);
}
};
public void star(){
handler.sendEmptyMessage(0);
}
说简单点就是通过 handle 重复发送延时消息添加一个 shop,然后再展示一段动画,最后在动画结束的时候 remove 掉就好。addHeart()方面里面代码量较多,具体实现代码就不贴出来了。
动画四:在刷新的回调中开启购物袋抖动效果,在刷新结束的时候关闭即可。这里只是做了一个简单的 Y轴缩放0.95的过程
ScaleAnimation scaleAnimation=new ScaleAnimation(1,1.0f,0.95f,1.0f,
Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scaleAnimation.setDuration(800);
scaleAnimation.setRepeatCount(100);//设置缩放次数
rongYi.startAnimation(scaleAnimation);
难点三解决:
此时,我们的下拉刷新 Demo 已经完成了,测试没问题之后就可以移植到项目中去了,傲娇的程序员肯定不会选择一个一个页面去修改控件,那是代码搬运工干的蠢事,我们直接全局搜索替换android.support.v4.widget.SwipeRefreshLayout就完工了,不会全局替换代码的小伙伴自己去问一下度娘吧~
到这里,本次UI 提出的更换下拉刷新效果的需求已经完成,再给大家回顾一次整个流程。首先拿到一个需求先不要慌,冷静下来好好分析,分析完了之后也不要急着敲代码,自己把整个思路再捋一遍,画个草图。最后,实际开发的过程中,能有现成的轮子可以用,就尽量利用,比如说这次的“难点一”,就直接用了现有的轮子。当然,时间充沛的前提下,自己造轮子也是可以的,这样对自身的提高会比较有帮助。
我的梦想是“没有bug”,感谢阅读。
网友评论