十分鐘學(xué)會(huì)定制 Android 酷炫下拉刷新。

這次UI又腦洞大開,嫌棄我們項(xiàng)目中的 SwipeRefreshLayout 的下拉效果沒特色,整了一個(gè)帶主題色彩的下拉刷新,效果圖如下:

想到又要手?jǐn)]一個(gè)下拉刷新,心里頓時(shí)上萬條草泥馬奔騰。做為一只傲嬌程序猿,我們怎么能干出這種吃力不討好的事呢(邏輯判斷、Touch事件分發(fā)、狀態(tài)變化寫起來很麻煩,還特么可能有一大堆八阿哥)。吃口屎冷靜了一下,先分析吧~

要實(shí)現(xiàn)這個(gè)下拉刷新,主要的難點(diǎn)有:

1、下拉刷新功能的實(shí)現(xiàn)

2、刷新過程中的 UI 動(dòng)效

3、 Demo 寫出來了,要是一個(gè)一個(gè)頁面去移植這個(gè)下拉刷新,那又得花不少時(shí)間,怎樣更快捷的方式替換掉項(xiàng)目中幾十上百個(gè)頁面。

解決思路:

難點(diǎn)一:說的下拉刷新的功能實(shí)現(xiàn),很多小伙伴都會(huì)說PullToRefreshListview、SwipeRefreshLayout呀,度娘一搜一大把。對,沒錯(cuò),我也是直接在 SwipeRefreshLayout 這個(gè)類上修改了刷新 Ui 效果。至于為什么不用PullToRefreshListview,因?yàn)槲覀冺?xiàng)目中還有很多頁面并不是列表頁,但是也需要刷新。

難點(diǎn)二:刷新過程中的 UI 動(dòng)效,這個(gè)后文我會(huì)帶著大家分析代碼實(shí)現(xiàn)。

難點(diǎn)三:全局搜索替換,把“android.support.v4.widget.SwipeRefreshLayout”替換成你的控件名就好,注意接口回調(diào)以及方法名和SwipeRefreshLayout保持一致即可。

好了,問題都已經(jīng)分析完了,接下來就擼代碼吧~

難點(diǎn)一解決:我的解決方案是直接修改了SwipeRefreshLayout,但是由于公司的源碼不方便拿出來講解,這里我在 Github 上隨便搜了一個(gè)和SwipeRefreshLayout 功能一樣的?Github Demo 來作講解,最終實(shí)現(xiàn)效果不好有任何影響,希望大家不要介懷。

使用起來很簡單

在 需要刷新的 View 父節(jié)點(diǎn)用 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代碼中設(shè)置自定義的下拉刷新頭即可。

RefreshLayout refreshLayout=(RefreshLayout)findViewById(R.id.refreshLayout);

ShopView shopView=new ShopView(this)

refreshLayout.setRefreshHeader(shopView);

其中RefreshLayout就是GitHub 上面搜索的和SwipeRefreshLayout相似的類,ShopView 就是我自己寫的自定義下拉刷新樣式。

RefreshLayout 功能:攔截Touch 事件根據(jù)判斷子 View 是否滑動(dòng)到頂部來判斷是否要顯示刷新頭,就是一個(gè)類似于RefreshLayout的容器。具體實(shí)現(xiàn)這里就不做重點(diǎn)講解了,有興趣的小伙伴自己自己下載下來讀一遍,源碼我會(huì)在文章結(jié)尾處貼出來。

ShopView:自定義的請求頭,難點(diǎn)二中會(huì)詳細(xì)講解ShopView的實(shí)現(xiàn)。

難點(diǎn)二解決:?

第一步:新建一個(gè) ShopView 類,繼承RelativeLayout(繼承 ViewGroup 也行,我嫌棄麻煩),然后在構(gòu)造方面里面 View.inflate一個(gè)布局到當(dāng)前 View。

第二步:讓布局里面的元素在合適的時(shí)候動(dòng)起來

首先我們來分析刷新頭。動(dòng)畫中的基本元素有:1.購物袋上的手提繩、2.“直擊全國專柜特賣現(xiàn)場”文字圖片、3.購物袋中噴出的商品、4.購物袋、

觀察 UI 效果圖我們發(fā)現(xiàn),購物袋需要在刷新的時(shí)候抖動(dòng)、購物袋上的手提繩需要在下拉的過程中改變效果、刷新的時(shí)候購物袋中會(huì)噴出商品、刷新頭完整出現(xiàn)之后繼續(xù)下拉會(huì)出現(xiàn)“直擊全國專柜特賣現(xiàn)場”。

由于這些元素都包含在刷新頭里面,但是上面的4種動(dòng)畫元素是需要根據(jù)不同的狀態(tài)來展示的,因此,我們需要一個(gè)接口來監(jiān)聽RefreshLayout中的狀態(tài)變化,這里原作者已經(jīng)提供了狀態(tài)回調(diào),我們直接用就行了。

public interface RefreshHeader{

/**

* 松手,頭部隱藏后會(huì)回調(diào)這個(gè)方法

*/

voidreset();

/**

* 下拉出頭部的一瞬間調(diào)用

*/

voidpull();

/**

* 正在刷新的時(shí)候調(diào)用

*/

voidrefreshing();

/**

* 頭部滾動(dòng)的時(shí)候持續(xù)調(diào)用

*

* @paramcurrentPostarget當(dāng)前偏移高度

* @paramlastPostarget上一次的偏移高度

* @paramrefreshPos可以松手刷新的高度

* @paramisTouch手指是否按下狀態(tài)(通過scroll自動(dòng)滾動(dòng)時(shí)需要判斷)

* @paramstate當(dāng)前狀態(tài)

*/

voidonPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate);

/**

* 刷新成功的時(shí)候調(diào)用

*/

voidcomplete();

}

直接讓我們的 ShopView 實(shí)現(xiàn)這個(gè)接口,在相應(yīng)的狀態(tài)回調(diào)里面展示相應(yīng)的動(dòng)畫即可。

接下來就只需要把這四個(gè)動(dòng)畫擼出來就完成我們的定制下拉刷新了~

動(dòng)畫一:手提繩的變化

首先,在onPositionChange()條目下拉的時(shí)候改變手提繩的效果。經(jīng)過觀察我們發(fā)現(xiàn)手提袋就是一個(gè)上下翻滾的效果,這里我們直接用一個(gè)二階貝塞爾曲線,起始點(diǎn)不動(dòng),控制點(diǎn) x 軸在起始點(diǎn)正中間,y 軸根據(jù)onPositionChange()變化而變化即可。實(shí)現(xiàn)核心代碼如下:

@Override

public void onPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate){

mBezierLine.setControlY(currentPos);

}

這里直接把手提袋封裝了一個(gè) View,setControlY方法實(shí)際上就是改變了控制點(diǎn)的 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);

}


動(dòng)畫二:當(dāng)條目下拉過程中超過 刷新頭的高度時(shí),改變“直擊全國專柜特賣現(xiàn)場”的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));

}

}

動(dòng)畫三:在刷新的回調(diào)中開啟噴出商品的動(dòng)畫,在刷新結(jié)束的時(shí)候關(guān)閉即可

@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);

}

說簡單點(diǎn)就是通過 handle 重復(fù)發(fā)送延時(shí)消息添加一個(gè) shop,然后再展示一段動(dòng)畫,最后在動(dòng)畫結(jié)束的時(shí)候 remove 掉就好。addHeart()方面里面代碼量較多,具體實(shí)現(xiàn)代碼就不貼出來了。

動(dòng)畫四:在刷新的回調(diào)中開啟購物袋抖動(dòng)效果,在刷新結(jié)束的時(shí)候關(guān)閉即可。這里只是做了一個(gè)簡單的 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);//設(shè)置縮放次數(shù)

rongYi.startAnimation(scaleAnimation);


難點(diǎn)三解決:

此時(shí),我們的下拉刷新 Demo 已經(jīng)完成了,測試沒問題之后就可以移植到項(xiàng)目中去了,傲嬌的程序員肯定不會(huì)選擇一個(gè)一個(gè)頁面去修改控件,那是代碼搬運(yùn)工干的蠢事,我們直接全局搜索替換android.support.v4.widget.SwipeRefreshLayout就完工了,不會(huì)全局替換代碼的小伙伴自己去問一下度娘吧~


到這里,本次UI 提出的更換下拉刷新效果的需求已經(jīng)完成,再給大家回顧一次整個(gè)流程。首先拿到一個(gè)需求先不要慌,冷靜下來好好分析,分析完了之后也不要急著敲代碼,自己把整個(gè)思路再捋一遍,畫個(gè)草圖。最后,實(shí)際開發(fā)的過程中,能有現(xiàn)成的輪子可以用,就盡量利用,比如說這次的“難點(diǎn)一”,就直接用了現(xiàn)有的輪子。當(dāng)然,時(shí)間充沛的前提下,自己造輪子也是可以的,這樣對自身的提高會(huì)比較有幫助。

我的夢想是“沒有bug”,感謝閱讀。

源碼在這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,034評論 25 709
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,158評論 22 665
  • 今天早上閱讀我叔的《我的父親》知道有簡書這個(gè)日記方式。
    喜洋洋秀強(qiáng)閱讀 250評論 0 0
  • 快養(yǎng)成早起來的習(xí)慣了,只要睡覺在12點(diǎn)前就可以 注冊了百家號和頭條號,像百度這個(gè)流量王低頭
    鄭州鏈家芮培豪閱讀 154評論 0 1
  • 我不會(huì)承認(rèn)的,大家的關(guān)注點(diǎn)全落在了她眉頭的兩點(diǎn)上了,淚奔~ 依然是沒時(shí)間看教程,純筆刷涂抹,抹得有點(diǎn)倉促,這次沒有...
    四月青閱讀 544評論 14 7

友情鏈接更多精彩內(nèi)容