zoukankan      html  css  js  c++  java
  • Android 使用NineOldAndroids实现绚丽的ListView左右滑动删除Item效果

    转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/18311877),请尊重他人的辛勤劳动成果,谢谢!

    今天还是给大家带来自定义控件的编写,自定义一个ListView的左右滑动删除Item的效果,这个效果之前已经实现过了,有兴趣的可以看下Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果,之前使用的是滑动类Scroller来实现的,但是看了下通知栏的左右滑动删除效果,确实很棒,当我们滑动Item超过一半的时候,item的透明度就变成了0,我们就知道抬起手指的时候item就被删除了,当item的透明度不为0的时候,我们抬起手指Item会回到起始位置,这样我们就知道拖动到什么位置item会删除,什么位置Item不删除,用户体验更好了,还有一个效果,就是我们滑动删除了item的时候,ListView的其他item会出现向上或者向下滚动的效果,感觉效果很棒,所以在GitHub上面搜索了下,发现很多开源库都有这个效果,比如ListViewAnimationsandroid-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库中,这是一个兼容库,接下来我们就来看看这个效果的具体实现吧

    实现该效果的主要思路

    1. 先根据手指触摸的点来获取点击的是ListView的哪一个Item
    2. 当手指在屏幕上面滑动的时候,我们要使得Item跟随手指的滑动而滑动
    3. 当我们抬起手指的时候,我们根据滑动的距离或者手指在屏幕上面的速度来判断Item是滑出屏幕还是滑动至其实位置
    4. Item滑出屏幕时,使ListView的其他item产生向上挤压或者向下挤压的效果

    大致的思路这是这四步,其中的一些细节接下来我会一一为大家解答的,接下来我们就用代码来实现这种效果吧

    首先我们新建一个工程,叫Swipedismisslistview,我们需要将Nine Old Androids这个库引入到工程,大家可以去https://github.com/JakeWharton/NineOldAndroids下载,可以使用Jar包,也可以使用工程库的形式引入到我们自己的工程,我们还需要自定义一个ListView,我们先看代码然后给大家讲解下具体的功能实现

    1. package com.example.swipedismisslistview;  
    2.   
    3. import static com.nineoldandroids.view.ViewHelper.setAlpha;  
    4. import static com.nineoldandroids.view.ViewHelper.setTranslationX;  
    5. import android.content.Context;  
    6. import android.util.AttributeSet;  
    7. import android.view.MotionEvent;  
    8. import android.view.VelocityTracker;  
    9. import android.view.View;  
    10. import android.view.ViewConfiguration;  
    11. import android.view.ViewGroup;  
    12. import android.widget.AdapterView;  
    13. import android.widget.ListView;  
    14.   
    15. import com.nineoldandroids.animation.Animator;  
    16. import com.nineoldandroids.animation.AnimatorListenerAdapter;  
    17. import com.nineoldandroids.animation.ValueAnimator;  
    18. import com.nineoldandroids.view.ViewHelper;  
    19. import com.nineoldandroids.view.ViewPropertyAnimator;  
    20. /** 
    21.  * @blog http://blog.csdn.net/xiaanming 
    22.  *  
    23.  * @author xiaanming 
    24.  * 
    25.  */  
    26. public class SwipeDismissListView extends ListView {  
    27.     /** 
    28.      * 认为是用户滑动的最小距离 
    29.      */  
    30.     private int mSlop;  
    31.     /** 
    32.      * 滑动的最小速度 
    33.      */  
    34.     private int mMinFlingVelocity;  
    35.     /** 
    36.      * 滑动的最大速度 
    37.      */  
    38.     private int mMaxFlingVelocity;  
    39.     /** 
    40.      * 执行动画的时间 
    41.      */  
    42.     protected long mAnimationTime = 150;  
    43.     /** 
    44.      * 用来标记用户是否正在滑动中 
    45.      */  
    46.     private boolean mSwiping;  
    47.     /** 
    48.      * 滑动速度检测类 
    49.      */  
    50.     private VelocityTracker mVelocityTracker;  
    51.     /** 
    52.      * 手指按下的position 
    53.      */  
    54.     private int mDownPosition;  
    55.     /** 
    56.      * 按下的item对应的View 
    57.      */  
    58.     private View mDownView;  
    59.     private float mDownX;  
    60.     private float mDownY;  
    61.     /** 
    62.      * item的宽度 
    63.      */  
    64.     private int mViewWidth;  
    65.     /** 
    66.      * 当ListView的Item滑出界面回调的接口 
    67.      */  
    68.     private OnDismissCallback onDismissCallback;  
    69.   
    70.     /** 
    71.      * 设置动画时间 
    72.      *  
    73.      * @param mAnimationTime 
    74.      */  
    75.     public void setmAnimationTime(long mAnimationTime) {  
    76.         this.mAnimationTime = mAnimationTime;  
    77.     }  
    78.   
    79.     /** 
    80.      * 设置删除回调接口 
    81.      *  
    82.      * @param onDismissCallback 
    83.      */  
    84.     public void setOnDismissCallback(OnDismissCallback onDismissCallback) {  
    85.         this.onDismissCallback = onDismissCallback;  
    86.     }  
    87.   
    88.     public SwipeDismissListView(Context context) {  
    89.         this(context, null);  
    90.     }  
    91.   
    92.     public SwipeDismissListView(Context context, AttributeSet attrs) {  
    93.         this(context, attrs, 0);  
    94.     }  
    95.   
    96.     public SwipeDismissListView(Context context, AttributeSet attrs,  
    97.             int defStyle) {  
    98.         super(context, attrs, defStyle);  
    99.   
    100.         ViewConfiguration vc = ViewConfiguration.get(context);  
    101.         mSlop = vc.getScaledTouchSlop();  
    102.         mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 8; //获取滑动的最小速度  
    103.         mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();  //获取滑动的最大速度  
    104.     }  
    105.   
    106.       
    107.     @Override  
    108.     public boolean onTouchEvent(MotionEvent ev) {  
    109.         switch (ev.getAction()) {  
    110.         case MotionEvent.ACTION_DOWN:  
    111.             handleActionDown(ev);  
    112.             break;  
    113.         case MotionEvent.ACTION_MOVE:  
    114.             return handleActionMove(ev);  
    115.         case MotionEvent.ACTION_UP:  
    116.             handleActionUp(ev);  
    117.             break;  
    118.         }  
    119.         return super.onTouchEvent(ev);  
    120.     }  
    121.   
    122.     /** 
    123.      * 按下事件处理 
    124.      *  
    125.      * @param ev 
    126.      * @return 
    127.      */  
    128.     private void handleActionDown(MotionEvent ev) {  
    129.         mDownX = ev.getX();  
    130.         mDownY = ev.getY();  
    131.           
    132.         mDownPosition = pointToPosition((int) mDownX, (int) mDownY);  
    133.   
    134.         if (mDownPosition == AdapterView.INVALID_POSITION) {  
    135.             return;  
    136.         }  
    137.   
    138.         mDownView = getChildAt(mDownPosition - getFirstVisiblePosition());  
    139.   
    140.         if (mDownView != null) {  
    141.             mViewWidth = mDownView.getWidth();  
    142.         }  
    143.   
    144.         //加入速度检测  
    145.         mVelocityTracker = VelocityTracker.obtain();  
    146.         mVelocityTracker.addMovement(ev);  
    147.     }  
    148.       
    149.   
    150.     /** 
    151.      * 处理手指滑动的方法 
    152.      *  
    153.      * @param ev 
    154.      * @return 
    155.      */  
    156.     private boolean handleActionMove(MotionEvent ev) {  
    157.         if (mVelocityTracker == null || mDownView == null) {  
    158.             return super.onTouchEvent(ev);  
    159.         }  
    160.   
    161.         // 获取X方向滑动的距离  
    162.         float deltaX = ev.getX() - mDownX;  
    163.         float deltaY = ev.getY() - mDownY;  
    164.   
    165.         // X方向滑动的距离大于mSlop并且Y方向滑动的距离小于mSlop,表示可以滑动  
    166.         if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop) {  
    167.             mSwiping = true;  
    168.               
    169.             //当手指滑动item,取消item的点击事件,不然我们滑动Item也伴随着item点击事件的发生  
    170.             MotionEvent cancelEvent = MotionEvent.obtain(ev);  
    171.             cancelEvent.setAction(MotionEvent.ACTION_CANCEL |  
    172.                        (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));  
    173.             onTouchEvent(cancelEvent);  
    174.         }  
    175.   
    176.         if (mSwiping) {  
    177.             // 跟谁手指移动item  
    178.             ViewHelper.setTranslationX(mDownView, deltaX);  
    179.             // 透明度渐变  
    180.             ViewHelper.setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX)/ mViewWidth)));  
    181.   
    182.             // 手指滑动的时候,返回true,表示SwipeDismissListView自己处理onTouchEvent,其他的就交给父类来处理  
    183.             return true;  
    184.         }  
    185.   
    186.         return super.onTouchEvent(ev);  
    187.   
    188.     }  
    189.   
    190.     /** 
    191.      * 手指抬起的事件处理 
    192.      * @param ev 
    193.      */  
    194.     private void handleActionUp(MotionEvent ev) {  
    195.         if (mVelocityTracker == null || mDownView == null|| !mSwiping) {  
    196.             return;  
    197.         }  
    198.   
    199.         float deltaX = ev.getX() - mDownX;  
    200.           
    201.         //通过滑动的距离计算出X,Y方向的速度  
    202.         mVelocityTracker.computeCurrentVelocity(1000);  
    203.         float velocityX = Math.abs(mVelocityTracker.getXVelocity());  
    204.         float velocityY = Math.abs(mVelocityTracker.getYVelocity());  
    205.           
    206.         boolean dismiss = false; //item是否要滑出屏幕  
    207.         boolean dismissRight = false;//是否往右边删除  
    208.           
    209.         //当拖动item的距离大于item的一半,item滑出屏幕  
    210.         if (Math.abs(deltaX) > mViewWidth / 2) {  
    211.             dismiss = true;  
    212.             dismissRight = deltaX > 0;  
    213.               
    214.             //手指在屏幕滑动的速度在某个范围内,也使得item滑出屏幕  
    215.         } else if (mMinFlingVelocity <= velocityX  
    216.                 && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {  
    217.             dismiss = true;  
    218.             dismissRight = mVelocityTracker.getXVelocity() > 0;  
    219.         }  
    220.   
    221.         if (dismiss) {  
    222.             ViewPropertyAnimator.animate(mDownView)  
    223.                     .translationX(dismissRight ? mViewWidth : -mViewWidth)//X轴方向的移动距离  
    224.                     .alpha(0)  
    225.                     .setDuration(mAnimationTime)  
    226.                     .setListener(new AnimatorListenerAdapter() {  
    227.                         @Override  
    228.                         public void onAnimationEnd(Animator animation) {  
    229.                             //Item滑出界面之后执行删除  
    230.                             performDismiss(mDownView, mDownPosition);  
    231.                         }  
    232.                     });  
    233.         } else {  
    234.             //将item滑动至开始位置  
    235.             ViewPropertyAnimator.animate(mDownView)  
    236.             .translationX(0)  
    237.             .alpha(1)     
    238.             .setDuration(mAnimationTime).setListener(null);  
    239.         }  
    240.           
    241.         //移除速度检测  
    242.         if(mVelocityTracker != null){  
    243.             mVelocityTracker.recycle();  
    244.             mVelocityTracker = null;  
    245.         }  
    246.           
    247.         mSwiping = false;  
    248.     }  
    249.       
    250.   
    251.       
    252.     /** 
    253.      * 在此方法中执行item删除之后,其他的item向上或者向下滚动的动画,并且将position回调到方法onDismiss()中 
    254.      * @param dismissView 
    255.      * @param dismissPosition 
    256.      */  
    257.     private void performDismiss(final View dismissView, final int dismissPosition) {  
    258.         final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();//获取item的布局参数  
    259.         final int originalHeight = dismissView.getHeight();//item的高度  
    260.   
    261.         ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 0).setDuration(mAnimationTime);  
    262.         animator.start();  
    263.   
    264.         animator.addListener(new AnimatorListenerAdapter() {  
    265.             @Override  
    266.             public void onAnimationEnd(Animator animation) {  
    267.                 if (onDismissCallback != null) {  
    268.                     onDismissCallback.onDismiss(dismissPosition);  
    269.                 }  
    270.   
    271.                 //这段代码很重要,因为我们并没有将item从ListView中移除,而是将item的高度设置为0  
    272.                 //所以我们在动画执行完毕之后将item设置回来  
    273.                 ViewHelper.setAlpha(dismissView, 1f);  
    274.                 ViewHelper.setTranslationX(dismissView, 0);  
    275.                 ViewGroup.LayoutParams lp = dismissView.getLayoutParams();  
    276.                 lp.height = originalHeight;  
    277.                 dismissView.setLayoutParams(lp);  
    278.   
    279.             }  
    280.         });  
    281.   
    282.         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    283.             @Override  
    284.             public void onAnimationUpdate(ValueAnimator valueAnimator) {  
    285.                 //这段代码的效果是ListView删除某item之后,其他的item向上滑动的效果  
    286.                 lp.height = (Integer) valueAnimator.getAnimatedValue();  
    287.                 dismissView.setLayoutParams(lp);  
    288.             }  
    289.         });  
    290.   
    291.     }  
    292.   
    293.     /** 
    294.      * 删除的回调接口 
    295.      *  
    296.      * @author xiaanming 
    297.      *  
    298.      */  
    299.     public interface OnDismissCallback {  
    300.         public void onDismiss(int dismissPosition);  
    301.     }  
    302.   
    303. }  

    看过Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果你会发现,这个自定义的SwipeDismissListView只重写了onTouchEvent()方法,其实我们重写这一个方法就能实现我们需要的效果

    1. 我们先看手指按下屏幕的处理方法handleActionDown();该方法里面根据我们手指按下的点根据pointToPosition()方法来获取我们点击的position,然后利用getChildAt()来获取我们按下的item的View对象,并且加入手指在屏幕滑动的速度检查,这一步相对来说还是比较简单


    2. 接下来就是手指在屏幕上面滑动的处理方法handleActionMove(),这个方法就稍微的复杂些,我们需要根据手指在X轴的滑动距离和Y轴的滑动距离来判断是ListView item的水平滑动还是ListView的上下滑动,当满足Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop这个条件时候,我们用一个布尔值mSwiping来标记Item现在处于水平滑动的状态,这时候我们需要处理Item跟随手指的滑动而滑动的逻辑,我们使用ViewHelper来处理Item的滑动逻辑,这个类会根据机器的SDK版本来判断使用Android系统的API还是NineOldandroids中自己实现的API使得View滑动的效果,NineOldandroids中主要使用Camera(可以实现各种复杂动画效果的类),我们直接使用ViewHelper的setTranslationX()和setAlpha()就实现了item滑动和透明度渐变的效果,为了使得我们在滑动item的时候,ListView不上下滚动,我们必须返回true来屏蔽ListView的上下滚动,这里需要我们要非常熟悉Android的事件分发机制,这里我就不说明了,大家不了解的去网上找找相关的文章看看
    还有一个问题,就是当我们滑动ListView的item的时候,会伴随着item的点击事件,这不是我们想要的效果,所以当Item滑动的时候我们需要取消ListView Item的点击事件

    3. 在看手指抬起的时候的处理方法handleActionUp(),这里面需要根据手指的滑动速度或者Item移动的距离来判断Item是滑出屏幕还是滑动至起始位置,并且要判断item向左还是向右滑出屏幕等等逻辑,具体的逻辑可以看代码,相信大家都看得懂.

    我这里要说说ViewPropertyAnimator类,这个类能更好的实现一个View同时进行多个动画的功能,当然我们也可以使用ObjectAnimator利用AnimatorSet来实现一个View上的多个同时进行的动画效果,例如我们可以将

    1. ViewPropertyAnimator.animate(mDownView)  
    2.         .translationX(dismissRight ? mViewWidth : -mViewWidth)//X轴方向的移动距离  
    3.         .alpha(0)  
    4.         .setDuration(mAnimationTime)  
    5.         .setListener(new AnimatorListenerAdapter() {  
    6.             @Override  
    7.             public void onAnimationEnd(Animator animation) {  
    8.                 //Item滑出界面之后执行删除  
    9.                 performDismiss(mDownView, mDownPosition);  
    10.             }  
    11.         });  

    替换成

    1. AnimatorSet set = new AnimatorSet();  
    2.             set.playTogether(ObjectAnimator.ofFloat(mDownView, "translationX", dismissRight ? mViewWidth : -mViewWidth),   
    3.                             ObjectAnimator.ofFloat(mDownView, "alpha", 0));  
    4.             set.setDuration(mAnimationTime).start();  
    5.             set.addListener(new AnimatorListenerAdapter() {  
    6.                         @Override  
    7.                         public void onAnimationEnd(Animator animation) {  
    8.                             //Item滑出界面之后执行删除  
    9.                             performDismiss(mDownView, mDownPosition);  
    10.                         }  
    11.                     });  

    在效果上面是一样的,但是ViewPropertyAnimator在性能上要比使用ObjectAnimator来实现多个同时进行的动画要高的多,举个例子,假如要对View使用移动和透明度的动画,使用ViewPropertyAnimator的话,某个时间点上我们只需要调用一次invalidate()方法刷新界面就行了,而使用ObjectAnimator的话,移动的动画需要调用invalidate(),透明度的动画也需要调用invalidate()方法,在性能上使用AnimationSet比ViewPropertyAnimator要低,但是有的时候我们还是需要使用ObjectAnimator,比如,在某个时间内,我们需要将View先变大在变小在变大等复杂情况,这时候ObjectAnimator就派上用场了,例如

    1. 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,看看下面这段代码

    1. ViewHelper.setAlpha(dismissView, 1f);  
    2.                 ViewHelper.setTranslationX(dismissView, 0);  
    3.                 ViewGroup.LayoutParams lp = dismissView.getLayoutParams();  
    4.                 lp.height = originalHeight;  
    5.                 dismissView.setLayoutParams(lp);  

    我们使用动画只是将item移动出了屏幕,并且将item的高度设置为了0,并没有将item的View从ListView中Remove掉,况且ListView也不能直接Remove掉Item的,只能将数据源删除,在调用notifyDataSetChanged()刷新,所以我们需要将刚刚滑出屏幕高度设置为0的Item恢复回来

    自定义控件的代码我们已经编写完了,接下来我们就要使用它了,先看界面的布局代码

    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    2.     xmlns:tools="http://schemas.android.com/tools"    
    3.     android:layout_width="match_parent"    
    4.     android:layout_height="match_parent">    
    5.     
    6.     <com.example.swipedismisslistview.SwipeDismissListView  
    7.         android:id="@+id/swipeDismissListView"    
    8.         android:layout_width="match_parent"    
    9.         android:layout_height="match_parent"   
    10.         android:listSelector="@android:color/transparent"  
    11.         android:cacheColorHint="@android:color/transparent">    
    12.     </com.example.swipedismisslistview.SwipeDismissListView>  
    13.     
    14. </RelativeLayout>    

    很简单,一个RelativeLayout包裹我们自定义的ListView控件,接下来就是主界面的代码编写,跟平常的ListView使用一样,但是我们需要设置OnDismissCallback()监听,在

    onDismiss()中删除该位置对于的数据,刷新ListView

    1. package com.example.swipedismisslistview;  
    2.   
    3. import java.util.ArrayList;  
    4. import java.util.List;  
    5.   
    6. import android.app.Activity;  
    7. import android.os.Bundle;  
    8. import android.view.View;  
    9. import android.widget.AdapterView;  
    10. import android.widget.AdapterView.OnItemClickListener;  
    11. import android.widget.ArrayAdapter;  
    12. import android.widget.Toast;  
    13.   
    14. import com.example.swipedismisslistview.SwipeDismissListView.OnDismissCallback;  
    15.   
    16. public class SwipeActivity extends Activity {  
    17.     private SwipeDismissListView swipeDismissListView;  
    18.     private ArrayAdapter<String> adapter;  
    19.     private List<String> dataSourceList = new ArrayList<String>();  
    20.   
    21.     @Override  
    22.     protected void onCreate(Bundle savedInstanceState) {  
    23.         super.onCreate(savedInstanceState);  
    24.         setContentView(R.layout.activity_swipe);  
    25.         init();  
    26.     }  
    27.   
    28.     private void init() {  
    29.         swipeDismissListView = (SwipeDismissListView) findViewById(R.id.swipeDismissListView);  
    30.         for (int i = 0; i < 20; i++) {  
    31.             dataSourceList.add("滑动删除" + i);  
    32.         }  
    33.   
    34.         adapter = new ArrayAdapter<String>(this,  
    35.                 android.R.layout.simple_list_item_1,  
    36.                 android.R.id.text1, dataSourceList);  
    37.           
    38.         swipeDismissListView.setAdapter(adapter);  
    39.           
    40.         swipeDismissListView.setOnDismissCallback(new OnDismissCallback() {  
    41.               
    42.             @Override  
    43.             public void onDismiss(int dismissPosition) {  
    44.                  adapter.remove(adapter.getItem(dismissPosition));   
    45.             }  
    46.         });  
    47.           
    48.           
    49.         swipeDismissListView.setOnItemClickListener(new OnItemClickListener() {  
    50.   
    51.             @Override  
    52.             public void onItemClick(AdapterView<?> parent, View view,  
    53.                     int position, long id) {  
    54.                 Toast.makeText(SwipeActivity.this, adapter.getItem(position), Toast.LENGTH_SHORT).show();  
    55.             }  
    56.         });  
    57.   
    58.     }  
    59.   
    60. }  

    所有的代码都已经编写完毕了,接下来就是运行工程,看看具体的效果是不是我们想要的

    好了,今天的讲解到这里结束了,有了NineOldAndroids我们可以在2.x的手机上面实现许多复杂的动画效果,文章也介绍了关于开源库NineOldAndroids使用的一些知识,文章有点长,希望读者还是先将文章看下,然后自己看看能不能自己实现出来,有什么不明白的地方请在下面留言,我会为大家解答的!

    项目源码,点击下载

    ps: 下载源码的时候运行出错,是因为我加入了NineOldAndroids的Jar包,然后又加入了NineOldAndroids工程库,主要是我写DEMO的时候为了方便看源码就导入了NineOldAndroids工程库,大家删除一个JAR包或者NineOldAndroids工程库 就能解决了

  • 相关阅读:
    CSS 的 outline 属性
    CSS3 的 boxsizing 属性
    CSS 颜色的使用
    动态添加tab(ext)
    showModalDialog()重新加载问题
    DBUtility.SQLServerHelper”的类型初始值设定项引发异常
    终于有了思路
    js判断上传的图片格式
    session丢失 frame
    动态添加tab(ext中的treePanel)
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/6110912.html
Copyright © 2011-2022 走看看