Android 自定義View實(shí)現(xiàn)抽屜效果
Android 自定義View實(shí)現(xiàn)抽屜效果
說明
- 這個自定義View,沒有處理好多點(diǎn)觸摸問題
- View跟著手指移動,沒有采用傳統(tǒng)的scrollBy方法,而是通過不停地重新布局子View的方式,來使得子View產(chǎn)生滾動效果menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight);
- 相應(yīng)的,由于沒有使用scrollBy方法,就沒有產(chǎn)生getScrollX值,所以不能通過Scroller的startScroll方法來完成手指離開后的平滑滾動效果,而是使用了Animation動畫的applyTransformation方法來完成插值,從而實(shí)現(xiàn)動畫效果
主要算法是:動畫當(dāng)前值=起始值+(目標(biāo)值-起始值)*interpolatedTime
其中interpolatedTime是一個0.0f~1.0f的數(shù)字,系統(tǒng)自己插值計算好了(默認(rèn)是線性變化的),當(dāng)然你可以自己寫插值器
/** * 由于上面不能使用scrollBy,那么這里就不能使用Scroller這個類來完成平滑移動了,還好我們有動畫 */ class MyAnimation extends Animation { private int viewCurrentLfet; private int viewStartLfet; private int viewTargetLfet; private int viewWidth; private View view; private int cha; public MyAnimation(View view, int viewStartLfet, int viewTargetLfet, int viewWidth) { this.view = view; this.viewStartLfet = viewStartLfet; this.viewTargetLfet = viewTargetLfet; this.viewWidth = viewWidth; cha = viewTargetLfet - viewStartLfet; setDuration(Math.abs(cha)); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); viewCurrentLfet = (int) (viewStartLfet + cha * interpolatedTime); view.layout(viewCurrentLfet, 0, viewCurrentLfet + viewWidth, menuHeight); } }
完整代碼
package com.sunshine.choutidemo; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.Transformation; /** * Created by a on 2016/8/15. */ public class ChouTiView extends ViewGroup { private View mainView; private View menuView; private int menuWidth; private int downX; private int lastX; private int moveX; private int deltaX; private int menuLeft; private int mainLeft; private int menuHeight; private int mainWidth; private int mainHeight; private int menuLeftBorder; private int mainLeftBorder; private int menuRightBorder; private int mainRightBorder; private int mMaxVelocity; private VelocityTracker mVelocityTracker; private int mPointerId; private float velocityX; private float velocityY; public ChouTiView(Context context) { super(context); init(); } public ChouTiView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { // 0.獲得此次最大速率 mMaxVelocity = ViewConfiguration.get(getContext()).getMaximumFlingVelocity(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mainView.measure(widthMeasureSpec, heightMeasureSpec); menuView.measure(widthMeasureSpec, heightMeasureSpec); // 獲得子View的正確寬度(只能獲取具體的數(shù)字值),但是不能這樣獲取高度,因?yàn)檫@里match—parent為-1 menuWidth = menuView.getLayoutParams().width; menuLeft = (int) (-menuWidth * 0.5); menuLeftBorder = (int) (-menuWidth * 0.5); menuRightBorder = 0; mainLeft = 0; mainLeftBorder = 0; mainRightBorder = menuWidth; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { menuHeight = b; mainWidth = r; mainHeight = b; mainView.layout(l, t, r, b); menuView.layout(menuLeft, t, menuLeft + menuWidth, b); } @Override protected void onFinishInflate() { super.onFinishInflate(); mainView = getChildAt(1); menuView = getChildAt(0); } @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); acquireVelocityTracker(event); //1.向VelocityTracker添加MotionEvent final VelocityTracker verTracker = mVelocityTracker; switch (action) { case MotionEvent.ACTION_DOWN: //2.求第一個觸點(diǎn)的id, 此時可能有多個觸點(diǎn),但至少一個 // 獲取索引為0的手指id mPointerId = event.getPointerId(0); downX = (int) event.getX(); lastX = downX; break; case MotionEvent.ACTION_MOVE: // 獲取當(dāng)前手指id所對應(yīng)的索引,雖然在ACTION_DOWN的時候,我們默認(rèn)選取索引為0 // 的手指,但當(dāng)有第二個手指觸摸,并且先前有效的手指up之后,我們會調(diào)整有效手指 // 屏幕上可能有多個手指,我們需要保證使用的是同一個手指的移動軌跡, // 因此此處不能使用event.getActionIndex()來獲得索引 final int pointerIndex = event.findPointerIndex(mPointerId); moveX = (int) event.getX(pointerIndex); deltaX = moveX - lastX; // 把觸摸移動引起的增量,體現(xiàn)在menu和main的左側(cè)left上 menuLeft = (int) (menuLeft + deltaX * 0.43);//讓菜單移動的慢一點(diǎn) mainLeft = mainLeft + deltaX; // 讓菜單根據(jù)手指增量移動,考慮兩側(cè)邊界問題(通過不停地layout實(shí)現(xiàn)移動效果) // 為何不適用scrollBy,因?yàn)閟crollBy移動的是外層的大View,現(xiàn)在需求是分別移動這個大view內(nèi)的兩個小View // scrollBy的話,會讓菜單和主頁面同時移動,不會產(chǎn)生錯位效果, // 你會想,那讓小view自己scrollBy,這樣也是不行的, // 因?yàn)樽屝iew,例如menu調(diào)用scrollBy的話,會讓menu自己的邊框在動, // 看上去,是menu內(nèi)部的文字在移動,但是menu并沒有在外層的大View里移動 // 說的很拗口,但是真的不能用scrollBy if (menuLeft >= menuRightBorder) { menuLeft = menuRightBorder; } else if (menuLeft <= menuLeftBorder) { menuLeft = menuLeftBorder; } menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight); // 讓主頁面根據(jù)手指增量移動,考慮兩側(cè)邊界問題 if (mainLeft >= mainRightBorder) { mainLeft = mainRightBorder; } else if (mainLeft <= mainLeftBorder) { mainLeft = mainLeftBorder; } mainView.layout(mainLeft, 0, mainLeft + mainWidth, mainHeight); lastX = moveX; break; case MotionEvent.ACTION_UP: //3.求偽瞬時速度 verTracker.computeCurrentVelocity(1000, mMaxVelocity); velocityX = verTracker.getXVelocity(mPointerId); Log.e("qwe", velocityX + "/" + mMaxVelocity); if (velocityX > 1000) { smoothToMenu(); } else if (velocityX < -2000) { smoothToMain(); } else { // 判斷松手的位置,如果大于1/2.5的菜單寬度就打開菜單,否則打開主頁面 if (mainLeft > menuWidth / 2.5) { Log.e("qqq", "顯示菜單"); smoothToMenu(); } else { Log.e("qqq", "顯示主頁面"); smoothToMain(); } } // 4.ACTION_UP釋放VelocityTracker,交給其他控件使用 releaseVelocityTracker(); break; case MotionEvent.ACTION_CANCEL: // 4.ACTION_UP釋放VelocityTracker,交給其他控件使用 releaseVelocityTracker(); case MotionEvent.ACTION_POINTER_UP: // 獲取離開屏幕的手指的索引 int pointerIndexLeave = event.getActionIndex(); int pointerIdLeave = event.getPointerId(pointerIndexLeave); if (mPointerId == pointerIdLeave) { // 離開屏幕的正是目前的有效手指,此處需要重新調(diào)整,并且需要重置VelocityTracker int reIndex = pointerIndexLeave == 0 ? 1 : 0; mPointerId = event.getPointerId(reIndex); // 調(diào)整觸摸位置,防止出現(xiàn)跳動 downX = (int) event.getX(reIndex); // y = event.getY(reIndex); releaseVelocityTracker(); } releaseVelocityTracker(); break; } return true; } private void smoothToMain() { MyAnimation menuAnimation = new MyAnimation(menuView, menuLeft, menuLeftBorder, menuWidth); MyAnimation mainAnimation = new MyAnimation(mainView, mainLeft, mainLeftBorder, mainWidth); AnimationSet animationSet = new AnimationSet(true); animationSet.addAnimation(menuAnimation); animationSet.addAnimation(mainAnimation); startAnimation(animationSet); //一定記得更新menu和main的左側(cè)狀態(tài),這影響到了,再次手指觸摸時候的動畫,否則突變 menuLeft = menuLeftBorder; mainLeft = mainLeftBorder; } private void smoothToMenu() { MyAnimation menuAnimation = new MyAnimation(menuView, menuLeft, menuRightBorder, menuWidth); MyAnimation mainAnimation = new MyAnimation(mainView, mainLeft, mainRightBorder, mainWidth); AnimationSet animationSet = new AnimationSet(true); animationSet.addAnimation(menuAnimation); animationSet.addAnimation(mainAnimation); startAnimation(animationSet); //一定記得更新menu和main的左側(cè)狀態(tài),這影響到了,再次手指觸摸時候的動畫,否則突變 menuLeft = menuRightBorder; mainLeft = mainRightBorder; } /** * @param event 向VelocityTracker添加MotionEvent * @see android.view.VelocityTracker#obtain() * @see android.view.VelocityTracker#addMovement(MotionEvent) */ private void acquireVelocityTracker(final MotionEvent event) { if (null == mVelocityTracker) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } /** * 釋放VelocityTracker * * @see android.view.VelocityTracker#clear() * @see android.view.VelocityTracker#recycle() */ private void releaseVelocityTracker() { if (null != mVelocityTracker) { mVelocityTracker.clear(); mVelocityTracker.recycle(); mVelocityTracker = null; } } /** * 由于上面不能使用scrollBy,那么這里就不能使用Scroller這個類來完成平滑移動了,還好我們有動畫 */ class MyAnimation extends Animation { private int viewCurrentLfet; private int viewStartLfet; private int viewTargetLfet; private int viewWidth; private View view; private int cha; public MyAnimation(View view, int viewStartLfet, int viewTargetLfet, int viewWidth) { this.view = view; this.viewStartLfet = viewStartLfet; this.viewTargetLfet = viewTargetLfet; this.viewWidth = viewWidth; cha = viewTargetLfet - viewStartLfet; setDuration(Math.abs(cha)); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); viewCurrentLfet = (int) (viewStartLfet + cha * interpolatedTime); view.layout(viewCurrentLfet, 0, viewCurrentLfet + viewWidth, menuHeight); } } }
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
- Android開發(fā)之DrawerLayout實(shí)現(xiàn)抽屜效果
- Android編程實(shí)現(xiàn)抽屜效果的方法詳解
- Android自定義控件仿QQ抽屜效果
- Android DrawerLayout實(shí)現(xiàn)抽屜效果實(shí)例代碼
- Android 抽屜效果的導(dǎo)航菜單實(shí)現(xiàn)代碼實(shí)例
- Android實(shí)現(xiàn)自定義滑動式抽屜菜單效果
- Android App中DrawerLayout抽屜效果的菜單編寫實(shí)例
- Android SlidingDrawer 抽屜效果的實(shí)現(xiàn)
- Android的Activity跳轉(zhuǎn)動畫各種效果整理
- Android Tween動畫之RotateAnimation實(shí)現(xiàn)圖片不停旋轉(zhuǎn)效果實(shí)例介紹
- Android實(shí)現(xiàn)圖片輪播效果的兩種方法
- Android編程實(shí)現(xiàn)抽屜效果的方法示例
相關(guān)文章
android將搜索引擎設(shè)置為中國雅虎無法搜索問題解決方法
android 進(jìn)入搜索,將搜索引擎設(shè)置為中國雅虎,無法搜索到相關(guān)網(wǎng)絡(luò)結(jié)果,該問題是由于yahoo的搜索接口改變導(dǎo)致,具體解決方法如下,感興趣的朋友可以參考下哈2013-06-06Android開發(fā)壁紙的驗(yàn)證設(shè)置和確認(rèn)功能實(shí)現(xiàn)demo
android?wallpaper包括鎖屏壁紙和桌面壁紙,壁紙又區(qū)分靜態(tài)和動態(tài)兩種。本文詳細(xì)介紹靜態(tài)壁紙?jiān)O(shè)置和確認(rèn),有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-04-04Android利用方向傳感器獲得手機(jī)的相對角度實(shí)例說明
下面以實(shí)例向大家介紹喜愛Android利用方向傳感器獲得手機(jī)的相對角度,不了解的朋友可以參考下2013-06-06android實(shí)現(xiàn)程序自動升級到安裝示例分享(下載android程序安裝包)
這篇文章主要介紹了android實(shí)現(xiàn)下載android程序安裝包自動升級的示例,大家參考使用吧2014-01-01Android原生TabLayout使用的超全解析(看這篇就夠了)
現(xiàn)在很多app都有頂部可左右切換的導(dǎo)航欄,并且還帶動畫效果,要實(shí)現(xiàn)這種導(dǎo)航欄,可以使用Android原生的Tablayout也可以借助第三方框架實(shí)現(xiàn),這篇文章主要給大家介紹了關(guān)于Android原生TabLayout使用的相關(guān)資料,需要的朋友可以參考下2022-09-09