Android 自定義View實現抽屜效果
說明
主要算法是:動畫當前值=起始值+(目標值-起始值)*interpolatedTime
其中interpolatedTime是一個0.0f~1.0f的數字,系統自己插值計算好了(默認是線性變化的),當然你可以自己寫插值器
/** * 由于上面不能使用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的正確寬度(只能獲取具體的數字值),但是不能這樣獲取高度,因為這里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.求第一個觸點的id, 此時可能有多個觸點,但至少一個 // 獲取索引為0的手指id mPointerId = event.getPointerId(0); downX = (int) event.getX(); lastX = downX; break; case MotionEvent.ACTION_MOVE:// 獲取當前手指id所對應的索引,雖然在ACTION_DOWN的時候,我們默認選取索引為0 // 的手指,但當有第二個手指觸摸,并且先前有效的手指up之后,我們會調整有效手指 // 屏幕上可能有多個手指,我們需要保證使用的是同一個手指的移動軌跡, // 因此此處不能使用event.getActionIndex()來獲得索引 final int pointerIndex = event.findPointerIndex(mPointerId); moveX = (int) event.getX(pointerIndex); deltaX = moveX - lastX;// 把觸摸移動引起的增量,體現在menu和main的左側left上 menuLeft = (int) (menuLeft + deltaX * 0.43);//讓菜單移動的慢一點 mainLeft = mainLeft + deltaX;// 讓菜單根據手指增量移動,考慮兩側邊界問題(通過不停地layout實現移動效果)// 為何不適用scrollBy,因為scrollBy移動的是外層的大View,現在需求是分別移動這個大view內的兩個小View// scrollBy的話,會讓菜單和主頁面同時移動,不會產生錯位效果,// 你會想,那讓小view自己scrollBy,這樣也是不行的,// 因為讓小view,例如menu調用scrollBy的話,會讓menu自己的邊框在動,// 看上去,是menu內部的文字在移動,但是menu并沒有在外層的大View里移動// 說的很拗口,但是真的不能用scrollBy if (menuLeft >= menuRightBorder) { menuLeft = menuRightBorder; } else if (menuLeft <= menuLeftBorder) { menuLeft = menuLeftBorder; } menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight);// 讓主頁面根據手指增量移動,考慮兩側邊界問題 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) { // 離開屏幕的正是目前的有效手指,此處需要重新調整,并且需要重置VelocityTracker int reIndex = pointerIndexLeave == 0 ? 1 : 0; mPointerId = event.getPointerId(reIndex); // 調整觸摸位置,防止出現跳動 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的左側狀態,這影響到了,再次手指觸摸時候的動畫,否則突變 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的左側狀態,這影響到了,再次手指觸摸時候的動畫,否則突變 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); } }}感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
新聞熱點
疑難解答