Android ReboundScrollView仿IOS拖拽回彈效果
初衷:
其實(shí)github上有很多這種ScrollView的項(xiàng)目,但是不得不說(shuō)功能太多太亂了,我就只是想要一個(gè)簡(jiǎn)單效果的ScrollView,另外監(jiān)聽下滑動(dòng)距離而已,想想還是自己寫了個(gè)。
這里先說(shuō)下思路吧,如果不愿意看的朋友可以直接跳過(guò)這一步,看下面的代碼:
Android 原生的ScrollView是不支持拉出屏幕外,并且也沒有回彈效果的,用戶友好度卻不不太好,不知道為什么不那么設(shè)計(jì)。
我想做的事情正如上面所述:
1.希望能拉出屏幕外
2.松手后希望控件回彈
我的思路是對(duì)ScrollView的子View進(jìn)行操作
所有View的滑動(dòng)控制肯定都受著onTouchEvent控制,所以,理所應(yīng)當(dāng)?shù)?,我要關(guān)注的重點(diǎn),也就是onTouchEvent這個(gè)方法。
回彈的效果,就牽涉到位置的計(jì)算,這里我的想法就用簡(jiǎn)單的TranslateAnimation來(lái)實(shí)現(xiàn)。
大體思路就是這樣了,先把思路確定了,然后代碼總是磨出來(lái)的。
貼代碼:
import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.TranslateAnimation; import android.widget.ScrollView; public class ReboundScrollView extends ScrollView { private static final float MOVE_DELAY = 0.3f;//當(dāng)拉出屏幕時(shí)的拖拽系數(shù) private static final int ANIM_TIME = 300;//回彈耗時(shí) private static final int FLING = 2;//fling 系數(shù) private View childView; private boolean havaMoved; private Rect originalRect = new Rect(); private float startY; @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() > 0) { childView = getChildAt(0); } } @Override public void fling(int velocityY) { super.fling(velocityY / 2); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (childView == null) return; originalRect.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom()); } public ReboundScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public ReboundScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public ReboundScrollView(Context context) { super(context); } /** * 在觸摸事件中, 處理上拉和下拉的邏輯 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (childView == null) { return super.dispatchTouchEvent(ev); } int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: startY = ev.getY(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (!havaMoved) break; TranslateAnimation anim = new TranslateAnimation(0, 0, childView.getTop(), originalRect.top); anim.setDuration(ANIM_TIME); childView.startAnimation(anim); // 將標(biāo)志位設(shè)回false havaMoved = false; resetViewLayout(); break; case MotionEvent.ACTION_MOVE: float nowY = ev.getY(); int deltaY = (int) (nowY - startY); int offset = (int) (deltaY * MOVE_DELAY); childView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset); havaMoved = true; break; default: break; } return super.dispatchTouchEvent(ev); } public void resetViewLayout() { childView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom); } }
把代碼貼出來(lái)后,再來(lái)分析具體的實(shí)現(xiàn):
首先是拉出屏幕,在MOVE的過(guò)程中,對(duì)于超出部分代碼,我使用layout重置子View的位置。
第二個(gè)要實(shí)現(xiàn)的就是回彈了,拖出去總是要回來(lái)的:
這里我定義了一個(gè)Rect,在onLayout(boolean changed, int l, int t, int r, int b)方法中,記錄下了ScrollView的初始位置,以便于重置回彈。
說(shuō)了許多,看一下代碼里的關(guān)鍵代碼
MOVE的代碼:
float nowY = ev.getY(); int deltaY = (int) (nowY - startY); int offset = (int) (deltaY * MOVE_DELAY); childView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset); havaMoved = true;
這是MotionEvent.ACTION_MOVE的時(shí)候要做的,對(duì)chlidView的位置重新設(shè)置也就是lauout方法,這是基于originalRect的值來(lái)的,設(shè)定了相應(yīng)的滑動(dòng)系數(shù),不然感覺實(shí)在是太靈敏了。
回彈的代碼:
if (!havaMoved) break; TranslateAnimation anim = new TranslateAnimation(0, 0, childView.getTop(), originalRect.top); anim.setDuration(ANIM_TIME); childView.startAnimation(anim); // 將標(biāo)志位設(shè)回false havaMoved = false; resetViewLayout(); ... ... public void resetViewLayout() { childView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom); }
當(dāng)手指 MotionEvent.ACTION_UP或者M(jìn)otionEvent.ACTION_CANCEL的時(shí)候把clildView置于原位,然后設(shè)置動(dòng)畫,這就是回彈了。
補(bǔ)充:
這是個(gè)非常簡(jiǎn)單的回彈ScrollView,需要注意的是,我這里沒有對(duì)在屏幕內(nèi)正常移動(dòng)和屏幕外移動(dòng)作區(qū)分,那樣就需要判斷當(dāng)前的移動(dòng)是否需要我們重寫了,也就是是否處于上拉,下拉的部分。沒有做判斷,我現(xiàn)在的代碼就會(huì)導(dǎo)致你手勢(shì)移動(dòng)的距離和控件滾動(dòng)的距離是不一樣的。我是感覺如果你正常使用是不會(huì)注意到這一點(diǎn)的。但是不排除有這種需求。
下拉其實(shí)不太好判斷,因?yàn)槲疫@里操作的是ChildView,ScrollView在上拉過(guò)程中g(shù)etScrollY()肯定是0,所以這一點(diǎn),我也還沒想好。不過(guò)上拉倒是比較簡(jiǎn)單。當(dāng)childView.height <= ScrollView.height + getScrollY的時(shí)候就是上拉出屏幕的點(diǎn)了,這個(gè)應(yīng)該能想通吧。
git地址:https://github.com/cjhandroid/ReboundScrollView
源碼下載:http://xiazai.jb51.net/201611/yuanma/androidReboundScrollView(jb51.net).rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android使用Shape實(shí)現(xiàn)ProgressBar樣式實(shí)例
本篇文章主要介紹了Android使用Shape實(shí)現(xiàn)ProgressBar樣式實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04Android簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)權(quán)限獲取相機(jī)權(quán)限及存儲(chǔ)空間等多權(quán)限
這篇文章主要介紹了Android簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)權(quán)限獲取相機(jī)權(quán)限及存儲(chǔ)空間等多權(quán)限,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-07-07Flutter 全局點(diǎn)擊空白處隱藏鍵盤實(shí)戰(zhàn)
這篇文章主要介紹了Flutter 全局點(diǎn)擊空白處隱藏鍵盤實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Android AIDL實(shí)現(xiàn)兩個(gè)APP間的跨進(jìn)程通信實(shí)例
這篇文章主要為大家詳細(xì)介紹了Android AIDL實(shí)現(xiàn)兩個(gè)APP間的跨進(jìn)程通信實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04Android實(shí)現(xiàn)簡(jiǎn)單的popupwindow提示框
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)單的popupwindow提示框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10Android UI:ListView - SimpleAdapter實(shí)例詳解
這篇文章主要介紹了Android UI:ListView - SimpleAdapter實(shí)例詳解,SimpleAdapter是擴(kuò)展性最好的適配器,可以定義各種你想要的布局,而且使用很方便,需要的朋友可以參考下2016-11-11Android 中RecyclerView多種item布局的寫法(頭布局+腳布局)
這篇文章主要介紹了Android 中RecyclerView多種item布局的寫法(頭布局+腳布局)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01OnSharedPreferenceChangeListener詳解及出現(xiàn)不觸發(fā)解決辦法
本文主要介紹 Android OnSharedPreferenceChangeListener的知識(shí),在Android應(yīng)用開發(fā)過(guò)程中會(huì)遇到監(jiān)聽器不觸發(fā)事件問(wèn)題,這里介紹了相應(yīng)的解決辦法2016-08-08