十分钟学会定制 Android 酷炫下拉刷新。

这次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”,感谢阅读。

源码在这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,047评论 25 707
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,658评论 22 664
  • 今天早上阅读我叔的《我的父亲》知道有简书这个日记方式。
    喜洋洋秀强阅读 230评论 0 0
  • 快养成早起来的习惯了,只要睡觉在12点前就可以 注册了百家号和头条号,像百度这个流量王低头
    郑州链家芮培豪阅读 133评论 0 1
  • 我不会承认的,大家的关注点全落在了她眉头的两点上了,泪奔~ 依然是没时间看教程,纯笔刷涂抹,抹得有点仓促,这次没有...
    四月青阅读 511评论 14 7