今天還是給大家帶來自定義控件的編寫,自定義一個ListView的左右滑動刪除Item的效果,這個效果之前已經實現過了,有興趣的可以看下Android 使用Scroller實現絢麗的ListView左右滑動刪除Item效果,之前使用的是滑動類Scroller來實現的,但是看了下通知欄的左右滑動刪除效果,確實很棒,當我們滑動Item超過一半的時候,item的透明度就變成了0,我們就知道抬起手指的時候item就被刪除了,當item的透明度不為0的時候,我們抬起手指Item會回到起始位置,這樣我們就知道拖動到什麼位置item會刪除,什麼位置Item不刪除,用戶體驗更好了,還有一個效果,就是我們滑動刪除了item的時候,ListView的其他item會出現向上或者向下滾動的效果,感覺效果很棒,所以在GitHub上面搜索了下,發現很多開源庫都有這個效果,比如ListViewAnimations,android-swipelistview等等,我看了下實現原理,使用的是Jake Wharton的動畫開源庫NineOldAndroids,這個庫究竟是干嘛的呢?在API3.0(Honeycomb), SDK新增了一個Android.animation包,裡面的類是實現動畫效果相關的類,通過Honeycomb API,能夠實現非常復雜的動畫效果,但是如果開發者想在3.0以下使用這一套API, 則需要使用開源框架Nine Old Androids,在這個庫中會根據我們運行的機器判斷其SDK版本,如果是API3.0以上則使用Android自帶的動畫類,否則就使用Nine Old Androids庫中,這是一個兼容庫,接下來我們就來看看這個效果的具體實現吧
實現該效果的主要思路
先根據手指觸摸的點來獲取點擊的是ListView的哪一個Item當手指在屏幕上面滑動的時候,我們要使得Item跟隨手指的滑動而滑動當我們抬起手指的時候,我們根據滑動的距離或者手指在屏幕上面的速度來判斷Item是滑出屏幕還是滑動至其實位置Item滑出屏幕時,使ListView的其他item產生向上擠壓或者向下擠壓的效果
大致的思路這是這四步,其中的一些細節接下來我會一一為大家解答的,接下來我們就用代碼來實現這種效果吧
首先我們新建一個工程,叫Swipedismisslistview,我們需要將Nine Old Androids這個庫引入到工程,大家可以去https://github.com/JakeWharton/NineOldAndroids下載,可以使用Jar包,也可以使用工程庫的形式引入到我們自己的工程,我們還需要自定義一個ListView,我們先看代碼然後給大家講解下具體的功能實現
- packagecom.example.swipedismisslistview;
-
- importstaticcom.nineoldandroids.view.ViewHelper.setAlpha;
- importstaticcom.nineoldandroids.view.ViewHelper.setTranslationX;
- importandroid.content.Context;
- importandroid.util.AttributeSet;
- importandroid.view.MotionEvent;
- importandroid.view.VelocityTracker;
- importandroid.view.View;
- importandroid.view.ViewConfiguration;
- importandroid.view.ViewGroup;
- importandroid.widget.AdapterView;
- importandroid.widget.ListView;
-
- importcom.nineoldandroids.animation.Animator;
- importcom.nineoldandroids.animation.AnimatorListenerAdapter;
- importcom.nineoldandroids.animation.ValueAnimator;
- importcom.nineoldandroids.view.ViewHelper;
- importcom.nineoldandroids.view.ViewPropertyAnimator;
- /**
- *@bloghttp://blog.csdn.net/xiaanming
- *
- *@authorxiaanming
- *
- */
- publicclassSwipeDismissListViewextendsListView{
- /**
- *認為是用戶滑動的最小距離
- */
- privateintmSlop;
- /**
- *滑動的最小速度
- */
- privateintmMinFlingVelocity;
- /**
- *滑動的最大速度
- */
- privateintmMaxFlingVelocity;
- /**
- *執行動畫的時間
- */
- protectedlongmAnimationTime=150;
- /**
- *用來標記用戶是否正在滑動中
- */
- privatebooleanmSwiping;
- /**
- *滑動速度檢測類
- */
- privateVelocityTrackermVelocityTracker;
- /**
- *手指按下的position
- */
- privateintmDownPosition;
- /**
- *按下的item對應的View
- */
- privateViewmDownView;
- privatefloatmDownX;
- privatefloatmDownY;
- /**
- *item的寬度
- */
- privateintmViewWidth;
- /**
- *當ListView的Item滑出界面回調的接口
- */
- privateOnDismissCallbackonDismissCallback;
-
- /**
- *設置動畫時間
- *
- *@parammAnimationTime
- */
- publicvoidsetmAnimationTime(longmAnimationTime){
- this.mAnimationTime=mAnimationTime;
- }
-
- /**
- *設置刪除回調接口
- *
- *@paramonDismissCallback
- */
- publicvoidsetOnDismissCallback(OnDismissCallbackonDismissCallback){
- this.onDismissCallback=onDismissCallback;
- }
-
- publicSwipeDismissListView(Contextcontext){
- this(context,null);
- }
-
- publicSwipeDismissListView(Contextcontext,AttributeSetattrs){
- this(context,attrs,0);
- }
-
- publicSwipeDismissListView(Contextcontext,AttributeSetattrs,
- intdefStyle){
- super(context,attrs,defStyle);
-
- ViewConfigurationvc=ViewConfiguration.get(context);
- mSlop=vc.getScaledTouchSlop();
- mMinFlingVelocity=vc.getScaledMinimumFlingVelocity()*8;//獲取滑動的最小速度
- mMaxFlingVelocity=vc.getScaledMaximumFlingVelocity();//獲取滑動的最大速度
- }
-
-
- @Override
- publicbooleanonTouchEvent(MotionEventev){
- switch(ev.getAction()){
- caseMotionEvent.ACTION_DOWN:
- handleActionDown(ev);
- break;
- caseMotionEvent.ACTION_MOVE:
- returnhandleActionMove(ev);
- caseMotionEvent.ACTION_UP:
- handleActionUp(ev);
- break;
- }
- returnsuper.onTouchEvent(ev);
- }
-
- /**
- *按下事件處理
- *
- *@paramev
- *@return
- */
- privatevoidhandleActionDown(MotionEventev){
- mDownX=ev.getX();
- mDownY=ev.getY();
-
- mDownPosition=pointToPosition((int)mDownX,(int)mDownY);
-
- if(mDownPosition==AdapterView.INVALID_POSITION){
- return;
- }
-
- mDownView=getChildAt(mDownPosition-getFirstVisiblePosition());
-
- if(mDownView!=null){
- mViewWidth=mDownView.getWidth();
- }
-
- //加入速度檢測
- mVelocityTracker=VelocityTracker.obtain();
- mVelocityTracker.addMovement(ev);
- }
-
-
- /**
- *處理手指滑動的方法
- *
- *@paramev
- *@return
- */
- privatebooleanhandleActionMove(MotionEventev){
- if(mVelocityTracker==null||mDownView==null){
- returnsuper.onTouchEvent(ev);
- }
-
- //獲取X方向滑動的距離
- floatdeltaX=ev.getX()-mDownX;
- floatdeltaY=ev.getY()-mDownY;
-
- //X方向滑動的距離大於mSlop並且Y方向滑動的距離小於mSlop,表示可以滑動
- if(Math.abs(deltaX)>mSlop&&Math.abs(deltaY) mSwiping=true;
-
- //當手指滑動item,取消item的點擊事件,不然我們滑動Item也伴隨著item點擊事件的發生
- MotionEventcancelEvent=MotionEvent.obtain(ev);
- cancelEvent.setAction(MotionEvent.ACTION_CANCEL|
- (ev.getActionIndex()< onTouchEvent(cancelEvent);
- }
-
- if(mSwiping){
- //跟誰手指移動item
- ViewHelper.setTranslationX(mDownView,deltaX);
- //透明度漸變
- ViewHelper.setAlpha(mDownView,Math.max(0f,Math.min(1f,1f-2f*Math.abs(deltaX)/mViewWidth)));
-
- //手指滑動的時候,返回true,表示SwipeDismissListView自己處理onTouchEvent,其他的就交給父類來處理
- returntrue;
- }
-
- returnsuper.onTouchEvent(ev);
-
- }
-
- /**
- *手指抬起的事件處理
- *@paramev
- */
- privatevoidhandleActionUp(MotionEventev){
- if(mVelocityTracker==null||mDownView==null||!mSwiping){
- return;
- }
-
- floatdeltaX=ev.getX()-mDownX;
-
- //通過滑動的距離計算出X,Y方向的速度
- mVelocityTracker.computeCurrentVelocity(1000);
- floatvelocityX=Math.abs(mVelocityTracker.getXVelocity());
- floatvelocityY=Math.abs(mVelocityTracker.getYVelocity());
-
- booleandismiss=false;//item是否要滑出屏幕
- booleandismissRight=false;//是否往右邊刪除
-
- //當拖動item的距離大於item的一半,item滑出屏幕
- if(Math.abs(deltaX)>mViewWidth/2){
- dismiss=true;
- dismissRight=deltaX>0;
-
- //手指在屏幕滑動的速度在某個范圍內,也使得item滑出屏幕
- }elseif(mMinFlingVelocity<=velocityX
- &&velocityX<=mMaxFlingVelocity&&velocityY dismiss=true;
- dismissRight=mVelocityTracker.getXVelocity()>0;
- }
-
- if(dismiss){
- ViewPropertyAnimator.animate(mDownView)
- .translationX(dismissRight?mViewWidth:-mViewWidth)//X軸方向的移動距離
- .alpha(0)
- .setDuration(mAnimationTime)
- .setListener(newAnimatorListenerAdapter(){
- @Override
- publicvoidonAnimationEnd(Animatoranimation){
- //Item滑出界面之後執行刪除
- performDismiss(mDownView,mDownPosition);
- }
- });
- }else{
- //將item滑動至開始位置
- ViewPropertyAnimator.animate(mDownView)
- .translationX(0)
- .alpha(1)
- .setDuration(mAnimationTime).setListener(null);
- }
-
- //移除速度檢測
- if(mVelocityTracker!=null){
- mVelocityTracker.recycle();
- mVelocityTracker=null;
- }
-
- mSwiping=false;
- }
-
-
-
- /**
- *在此方法中執行item刪除之後,其他的item向上或者向下滾動的動畫,並且將position回調到方法onDismiss()中
- *@paramdismissView
- *@paramdismissPosition
- */
- privatevoidperformDismiss(finalViewdismissView,finalintdismissPosition){
- finalViewGroup.LayoutParamslp=dismissView.getLayoutParams();//獲取item的布局參數
- finalintoriginalHeight=dismissView.getHeight();//item的高度
-
- ValueAnimatoranimator=ValueAnimator.ofInt(originalHeight,0).setDuration(mAnimationTime);
- animator.start();
-
- animator.addListener(newAnimatorListenerAdapter(){
- @Override
- publicvoidonAnimationEnd(Animatoranimation){
- if(onDismissCallback!=null){
- onDismissCallback.onDismiss(dismissPosition);
- }
-
- //這段代碼很重要,因為我們並沒有將item從ListView中移除,而是將item的高度設置為0
- //所以我們在動畫執行完畢之後將item設置回來
- ViewHelper.setAlpha(dismissView,1f);
- ViewHelper.setTranslationX(dismissView,0);
- ViewGroup.LayoutParamslp=dismissView.getLayoutParams();
- lp.height=originalHeight;
- dismissView.setLayoutParams(lp);
-
- }
- });
-
- animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){
- @Override
- publicvoidonAnimationUpdate(ValueAnimatorvalueAnimator){
- //這段代碼的效果是ListView刪除某item之後,其他的item向上滑動的效果
- lp.height=(Integer)valueAnimator.getAnimatedValue();
- dismissView.setLayoutParams(lp);
- }
- });
-
- }
-
- /**
- *刪除的回調接口
- *
- *@authorxiaanming
- *
- */
- publicinterfaceOnDismissCallback{
- publicvoidonDismiss(intdismissPosition);
- }
-
- }
看過Android 使用Scroller實現絢麗的ListView左右滑動刪除Item效果你會發現,這個自定義的SwipeDismissListView只重寫了onTouchEvent()方法,其實我們重寫這一個方法就能實現我們需要的效果
- ViewPropertyAnimator.animate(mDownView)
- .translationX(dismissRight?mViewWidth:-mViewWidth)//X軸方向的移動距離
- .alpha(0)
- .setDuration(mAnimationTime)
- .setListener(newAnimatorListenerAdapter(){
- @Override
- publicvoidonAnimationEnd(Animatoranimation){
- //Item滑出界面之後執行刪除
- performDismiss(mDownView,mDownPosition);
- }
- });
替換成
- AnimatorSetset=newAnimatorSet();
- set.playTogether(ObjectAnimator.ofFloat(mDownView,"translationX",dismissRight?mViewWidth:-mViewWidth),
- ObjectAnimator.ofFloat(mDownView,"alpha",0));
- set.setDuration(mAnimationTime).start();
- set.addListener(newAnimatorListenerAdapter(){
- @Override
- publicvoidonAnimationEnd(Animatoranimation){
- //Item滑出界面之後執行刪除
- performDismiss(mDownView,mDownPosition);
- }
- });
在效果上面是一樣的,但是ViewPropertyAnimator在性能上要比使用ObjectAnimator來實現多個同時進行的動畫要高的多,舉個例子,假如要對View使用移動和透明度的動畫,使用ViewPropertyAnimator的話,某個時間點上我們只需要調用一次invalidate()方法刷新界面就行了,而使用ObjectAnimator的話,移動的動畫需要調用invalidate(),透明度的動畫也需要調用invalidate()方法,在性能上使用AnimationSet比ViewPropertyAnimator要低,但是有的時候我們還是需要使用ObjectAnimator,比如,在某個時間內,我們需要將View先變大在變小在變大等復雜情況,這時候ObjectAnimator就派上用場了,例如
- ObjectAnimator.ofInt(mDownView,"scaleX",0,100,0,100).setDuration(100).start()
通過上面的幾步我們就實現了ListView的左右滑動刪除item的效果啦,但是還有一個效果,item刪除之後,ListView的其他item向上或者向下緩緩滑動的效果,實現這個也很容易,就是動態設置item的高度,item高度逐漸變小,這樣其他的item就會出現向上或者向下擠壓的效果啦!
4. 這裡我們使用的是ValueAnimator這個類,這個類並不是針對View作用的動畫,而是對某個值作用的動畫,他默認使用的Interpolator(插補器)是AccelerateDecelerateInterpolator(開始和結束的時候慢,中間快) ,舉個很簡單的例子,我們在10秒內使用ValueAnimator將某個值從0變化到100,如果使用LinearInterpolator(線性插補器,勻速變化)在第2秒的時候,這個值變成了20,而是用AccelerateDecelerateInterpolator,可能在第二秒的時候這個值為15或者13,所以我們在ValueAnimator變化的時候設置值動畫變化的監聽器AnimatorUpdateListener就知道某個時間這個值變成了多少,從而對View的某個屬性進行設置(例如大小),所以ValueAnimator是間接的對View設置動畫的
了解了ValueAnimator的使用原理,我們就可以現實上面的動畫效果了,我們使用ValueAnimator將item的高度變成0,設置ValueAnimator變化的監聽,我們在回調函數onAnimationUpdate()中動態的設置item的高度, 然後添加AnimatorListener監聽動畫的狀態(例如動畫開始,結束,重復等)監聽,在動畫結束的回調函數onAnimationEnd()中刪除該item的數據,調用notifyDataSetChanged刷新ListView,看看下面這段代碼
- ViewHelper.setAlpha(dismissView,1f);
- ViewHelper.setTranslationX(dismissView,0);
- ViewGroup.LayoutParamslp=dismissView.getLayoutParams();
- lp.height=originalHeight;
- dismissView.setLayoutParams(lp);
我們使用動畫只是將item移動出了屏幕,並且將item的高度設置為了0,並沒有將item的View從ListView中Remove掉,況且ListView也不能直接Remove掉Item的,只能將數據源刪除,在調用notifyDataSetChanged()刷新,所以我們需要將剛剛滑出屏幕高度設置為0的Item恢復回來
自定義控件的代碼我們已經編寫完了,接下來我們就要使用它了,先看界面的布局代碼
-
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- android:id="@+id/swipeDismissListView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:listSelector="@android:color/transparent"
- android:cacheColorHint="@android:color/transparent">
-
-
很簡單,一個RelativeLayout包裹我們自定義的ListView控件,接下來就是主界面的代碼編寫,跟平常的ListView使用一樣,但是我們需要設置OnDismissCallback()監聽,在
onDismiss()中刪除該位置對於的數據,刷新ListView
- packagecom.example.swipedismisslistview;
-
- importjava.util.ArrayList;
- importjava.util.List;
-
- importandroid.app.Activity;
- importandroid.os.Bundle;
- importandroid.view.View;
- importandroid.widget.AdapterView;
- importandroid.widget.AdapterView.OnItemClickListener;
- importandroid.widget.ArrayAdapter;
- importandroid.widget.Toast;
-
- importcom.example.swipedismisslistview.SwipeDismissListView.OnDismissCallback;
-
- publicclassSwipeActivityextendsActivity{
- privateSwipeDismissListViewswipeDismissListView;
- privateArrayAdapteradapter;
- privateListdataSourceList=newArrayList();
-
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_swipe);
- init();
- }
-
- privatevoidinit(){
- swipeDismissListView=(SwipeDismissListView)findViewById(R.id.swipeDismissListView);
- for(inti=0;i<20;i++){
- dataSourceList.add("滑動刪除"+i);
- }
-
- adapter=newArrayAdapter(this,
- android.R.layout.simple_list_item_1,
- android.R.id.text1,dataSourceList);
-
- swipeDismissListView.setAdapter(adapter);
-
- swipeDismissListView.setOnDismissCallback(newOnDismissCallback(){
-
- @Override
- publicvoidonDismiss(intdismissPosition){
- adapter.remove(adapter.getItem(dismissPosition));
- }
- });
-
-
- swipeDismissListView.setOnItemClickListener(newOnItemClickListener(){
-
- @Override
- publicvoidonItemClick(AdapterViewparent,Viewview,
- intposition,longid){
- Toast.makeText(SwipeActivity.this,adapter.getItem(position),Toast.LENGTH_SHORT).show();
- }
- });
-
- }
-
- }
所有的代碼都已經編寫完畢了,接下來就是運行工程,看看具體的效果是不是我們想要的