1. 程式人生 > >APP實用開發—自定義載入動畫

APP實用開發—自定義載入動畫

彷百度外賣動畫

動畫

這裡寫圖片描述
我們先來看看Android中的動畫吧:
Android中的動畫分為三種:

Tween動畫,這一類的動畫提供了旋轉、平移、縮放等效果。
Alpha – 淡入淡出
Scale – 縮放效果
Roate – 旋轉效果
Translate – 平移效果
Frame動畫(幀動畫),這一類動畫可以建立一個Drawable序列,按照指定時間間歇一個一個顯示出來。
Property動畫(屬性動畫),Android3.0之後引入出來的屬性動畫,它更改的是物件的實際屬性。

分析

我們可以看到百度外賣的下拉重新整理的頭是一個騎車的快遞員在路上疾行,分析一下我們得到下面的動畫:

背景圖片的平移動畫
太陽的自旋轉動畫
兩個小輪子的自旋轉動畫
這就很簡單了,接下來我們去百度外面的圖片資原始檔裡找到這幾張圖片:(下載百度外賣的apk直接解壓即可)
這裡寫圖片描述
定義下拉重新整理標頭檔案:headview.xml
這裡注意一下:我們定義了兩張背景圖片的ImageView是為了可以實現背景的平移動畫效果。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width
="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<ImageView android:id="@+id/iv_back1" android:layout_width="match_parent" android:layout_height="100dp" android:src="@drawable/pull_back" /> <ImageView android:id
="@+id/iv_back2" android:layout_width="match_parent" android:layout_height="100dp" android:src="@drawable/pull_back" />
<RelativeLayout android:id="@+id/main" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true"> <ImageView android:id="@+id/iv_rider" android:layout_width="50dp" android:layout_height="50dp" android:layout_marginTop="45dp" android:background="@drawable/pull_rider" /> <ImageView android:id="@+id/wheel1" android:layout_width="15dp" android:layout_height="15dp" android:layout_marginLeft="10dp" android:layout_marginTop="90dp" android:background="@drawable/pull_wheel" /> <ImageView android:id="@+id/wheel2" android:layout_width="15dp" android:layout_height="15dp" android:layout_marginLeft="40dp" android:layout_marginTop="90dp" android:background="@drawable/pull_wheel" /> </RelativeLayout> <ImageView android:id="@+id/ivsun" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginTop="20dp" android:layout_toRightOf="@+id/main" android:background="@drawable/pull_sun" /> </RelativeLayout>

接下來我們定義動畫效果:

背景圖片的平移效果:
實現兩個animation xml檔案,一個起始位置在100%,結束位置在0%,設定repeat屬性為迴圈往復。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator">

 <translate android:fromXDelta="100%p"  
 android:toXDelta="0%p"  
 android:repeatMode="restart"  
 android:interpolator="@android:anim/linear_interpolator" 
  android:repeatCount="infinite" android:duration="5000" />
</set>

另一個起始位置在0%,結束位置在-100%

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator">

    <translate android:fromXDelta="0%p" 
     android:toXDelta="-100%p"  
     android:repeatMode="restart" android:interpolator="@android:anim/linear_interpolator"     android:repeatCount="infinite"  
     android:duration="5000" />
</set>

太陽圍繞中心旋轉動畫:
從0-360度開始迴圈旋轉,旋轉所用時間為1s,旋轉中心距離view的左定點上邊緣為50%的距離,也就是正中心。

下面是具體屬性:

android:fromDegrees 起始的角度度數

android:toDegrees 結束的角度度數,負數表示逆時針,正數表示順時針。如10圈則比android:fromDegrees大3600即可

android:pivotX 旋轉中心的X座標

浮點數或是百分比。浮點數表示相對於Object的左邊緣,如5; 百分比表示相對於Object的左邊緣,如5%; 另一種百分比表示相對於父容器的左邊緣,如5%p; 一般設定為50%表示在Object中心

android:pivotY 旋轉中心的Y座標

浮點數或是百分比。浮點數表示相對於Object的上邊緣,如5; 百分比表示相對於Object的上邊緣,如5%; 另一種百分比表示相對於父容器的上邊緣,如5%p; 一般設定為50%表示在Object中心

android:duration 表示從android:fromDegrees轉動到android:toDegrees所花費的時間,單位為毫秒。可以用來計算速度。

android:interpolator表示變化率,但不是執行速度。一個插補屬性,可以將動畫效果設定為加速,減速,反覆,反彈等。預設為開始和結束慢中間快,

android:startOffset 在呼叫start函式之後等待開始執行的時間,單位為毫秒,若為10,表示10ms後開始執行

android:repeatCount 重複的次數,預設為0,必須是int,可以為-1表示不停止

android:repeatMode 重複的模式,預設為restart,即重頭開始重新執行,可以為reverse即從結束開始向前重新執行。在android:repeatCount大於0或為infinite時生效

android:detachWallpaper 表示是否在桌布上執行

android:zAdjustment 表示被animated的內容在執行時在z軸上的位置,預設為normalnormal保持內容當前的z軸順序

top執行時在最頂層顯示

bottom執行時在最底層顯示
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <rotate  android:fromDegrees="0"  
    android:toDegrees="360"  
    android:duration="1000" 
     android:repeatCount="-1"  
     android:pivotX="50%"  
     android:pivotY="50%" />
</set>

同理輪子的動畫也一樣,不佔程式碼了。

動畫定義完了我們開始定義下拉重新整理列表,下拉重新整理網上有很多,不詳細的說了,簡單的改造一下,根據重新整理狀態開啟關閉動畫即可。

public class BaiDuRefreshListView extends ListView implements AbsListView.OnScrollListener{
    private static final int DONE = 0;      //重新整理完畢狀態
    private static final int PULL_TO_REFRESH = 1;   //下拉重新整理狀態
    private static final int RELEASE_TO_REFRESH = 2;    //釋放狀態
    private static final int REFRESHING = 3;    //正在重新整理狀態
    private static final int RATIO = 3;
    private RelativeLayout headView;    //下拉重新整理頭
    private int headViewHeight; //頭高度
    private float startY;   //開始Y座標
    private float offsetY;  //Y軸偏移量
    private OnBaiduRefreshListener mOnRefreshListener;  //重新整理介面
    private int state;  //狀態值
    private int mFirstVisibleItem;  //第一項可見item索引
    private boolean isRecord;   //是否記錄
    private boolean isEnd;  //是否結束
    private boolean isRefreable;    //是否重新整理

    private ImageView ivWheel1,ivWheel2;    //輪組圖片元件
    private ImageView ivRider;  //騎手圖片元件
    private ImageView ivSun,ivBack1,ivBack2;    //太陽、背景圖片1、背景圖片2
    private Animation wheelAnimation,sunAnimation;  //輪子、太陽動畫
    private Animation backAnimation1,backAnimation2;    //兩張背景圖動畫

    public BaiDuRefreshListView(Context context) {
        super(context);
        init(context);
    }

    public BaiDuRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public BaiDuRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public interface OnBaiduRefreshListener{
        void onRefresh();
    }

    /** * 回撥介面,想實現下拉重新整理的listview實現此介面 * @param onRefreshListener */
    public void setOnBaiduRefreshListener(OnBaiduRefreshListener onRefreshListener){
        mOnRefreshListener = onRefreshListener;
        isRefreable = true;
    }

    /** * 重新整理完畢,從主執行緒傳送過來,並且改變headerView的狀態和文字動畫資訊 */
    public void setOnRefreshComplete(){
        //一定要將isEnd設定為true,以便於下次的下拉重新整理
        isEnd = true;
        state = DONE;

        changeHeaderByState(state);
    }

    private void init(Context context) {
        //關閉view的OverScroll
        setOverScrollMode(OVER_SCROLL_NEVER);
        setOnScrollListener(this);
        //載入頭佈局
        headView = (RelativeLayout) LayoutInflater.from(context).inflate(R.layout.headview,this,false);
        //測量頭佈局
        measureView(headView);
        //給ListView新增頭佈局
        addHeaderView(headView);
        //設定標頭檔案隱藏在ListView的第一項
        headViewHeight = headView.getMeasuredHeight();
        headView.setPadding(0, -headViewHeight, 0, 0);

        //獲取頭佈局圖片元件
        ivRider = (ImageView) headView.findViewById(R.id.iv_rider);
        ivSun = (ImageView) headView.findViewById(R.id.ivsun);
        ivWheel1 = (ImageView) headView.findViewById(R.id.wheel1);
        ivWheel2 = (ImageView) headView.findViewById(R.id.wheel2);
        ivBack1 = (ImageView) headView.findViewById(R.id.iv_back1);
        ivBack2 = (ImageView) headView.findViewById(R.id.iv_back2);
        //獲取動畫
        wheelAnimation = AnimationUtils.loadAnimation(context, R.anim.tip);
        sunAnimation = AnimationUtils.loadAnimation(context, R.anim.tip1);

        backAnimation1 = AnimationUtils.loadAnimation(context, R.anim.a);
        backAnimation2 = AnimationUtils.loadAnimation(context, R.anim.b);

        state = DONE;
        isEnd = true;
        isRefreable = false;


    }

    @Override
    public void onScrollStateChanged(AbsListView absListView, int i) {
    }
    @Override
    public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mFirstVisibleItem = firstVisibleItem;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (isEnd) {//如果現在時結束的狀態,即重新整理完畢了,可以再次重新整理了,在onRefreshComplete中設定
            if (isRefreable) {//如果現在是可重新整理狀態 在setOnMeiTuanListener中設定為true
                switch (ev.getAction()){
                    //使用者按下
                    case MotionEvent.ACTION_DOWN:
                        //如果當前是在listview頂部並且沒有記錄y座標
                        if (mFirstVisibleItem == 0 && !isRecord) {
                            //將isRecord置為true,說明現在已記錄y座標
                            isRecord = true;
                            //將當前y座標賦值給startY起始y座標
                            startY = ev.getY();
                        }
                        break;
                    //使用者滑動
                    case MotionEvent.ACTION_MOVE:
                        //再次得到y座標,用來和startY相減來計算offsetY位移值
                        float tempY = ev.getY();
                        //再起判斷一下是否為listview頂部並且沒有記錄y座標
                        if (mFirstVisibleItem == 0 && !isRecord) {
                            isRecord = true;
                            startY = tempY;
                        }
                        //如果當前狀態不是正在重新整理的狀態,並且已經記錄了y座標
                        if (state!=REFRESHING && isRecord ) {
                            //計算y的偏移量
                            offsetY = tempY - startY;
                            //計算當前滑動的高度
                            float currentHeight = (-headViewHeight+offsetY/3);
                            //用當前滑動的高度和頭部headerView的總高度進行比 計算出當前滑動的百分比 0到1
                            float currentProgress = 1+currentHeight/headViewHeight;
                            //如果當前百分比大於1了,將其設定為1,目的是讓第一個狀態的橢圓不再繼續變大
                            if (currentProgress>=1) {
                                currentProgress = 1;
                            }
                            //如果當前的狀態是放開重新整理,並且已經記錄y座標
                            if (state == RELEASE_TO_REFRESH && isRecord) {

                                setSelection(0);
                                //如果當前滑動的距離小於headerView的總高度
                                if (-headViewHeight+offsetY/RATIO<0) {
                                    //將狀態置為下拉重新整理狀態
                                    state = PULL_TO_REFRESH;
                                    //根據狀態改變headerView,主要是更新動畫和文字等資訊
                                    changeHeaderByState(state);
                                    //如果當前y的位移值小於0,即為headerView隱藏了
                                }else if (offsetY<=0) {
                                    //將狀態變為done
                                    state = DONE;
                                    stopAnim();
                                    //根據狀態改變headerView,主要是更新動畫和文字等資訊
                                    changeHeaderByState(state);
                                }
                            }
                            //如果當前狀態為下拉重新整理並且已經記錄y座標
                            if (state == PULL_TO_REFRESH && isRecord) {
                                setSelection(0);
                                //如果下拉距離大於等於headerView的總高度
                                if (-headViewHeight+offsetY/RATIO>=0) {
                                    //將狀態變為放開重新整理
                                    state = RELEASE_TO_REFRESH;
                                    //根據狀態改變headerView,主要是更新動畫和文字等資訊
                                    changeHeaderByState(state);
                                    //如果當前y的位移值小於0,即為headerView隱藏了
                                }else if (offsetY<=0) {
                                    //將狀態變為done
                                    state = DONE;
                                    //根據狀態改變headerView,主要是更新動畫和文字等資訊
                                    changeHeaderByState(state);
                                }
                            }
                            //如果當前狀態為done並且已經記錄y座標
                            if (state == DONE && isRecord) {
                                //如果位移值大於0
                                if (offsetY>=0) {
                                    //將狀態改為下拉重新整理狀態
                                    state = PULL_TO_REFRESH;
                                    changeHeaderByState(state);
                                }
                            }
                            //如果為下拉重新整理狀態
                            if (state == PULL_TO_REFRESH) {
                                //則改變headerView的padding來實現下拉的效果
                                headView.setPadding(0,(int)(-headViewHeight+offsetY/RATIO) ,0,0);
                            }
                            //如果為放開重新整理狀態
                            if (state == RELEASE_TO_REFRESH) {
                                //改變headerView的padding值
                                headView.setPadding(0,(int)(-headViewHeight+offsetY/RATIO) ,0, 0);
                            }
                        }
                        break;
                    //當用戶手指擡起時
                    case MotionEvent.ACTION_UP:
                        //如果當前狀態為下拉重新整理狀態
                        if (state == PULL_TO_REFRESH) {
                            //平滑的隱藏headerView
                            this.smoothScrollBy((int)(-headViewHeight+offsetY/RATIO)+headViewHeight, 500);
                            //根據狀態改變headerView
                            changeHeaderByState(state);
                        }
                        //如果當前狀態為放開重新整理
                        if (state == RELEASE_TO_REFRESH) {
                            //平滑的滑到正好顯示headerView
                            this.smoothScrollBy((int)(-headViewHeight+offsetY/RATIO), 500);
                            //將當前狀態設定為正在重新整理
                            state = REFRESHING;
                            //回撥介面的onRefresh方法
                            mOnRefreshListener.onRefresh();
                            //根據狀態改變headerView
                            changeHeaderByState(state);
                        }
                        //這一套手勢執行完,一定別忘了將記錄y座標的isRecord改為false,以便於下一次手勢的執行
                        isRecord = false;
                        break;
                }

            }
        }
        return super.onTouchEvent(ev);
    }

    /** * 根據狀態改變headerView的動畫和文字顯示 * @param state */
    private void changeHeaderByState(int state){
        switch (state) {
            case DONE://如果的隱藏的狀態
                //設定headerView的padding為隱藏
                headView.setPadding(0, -headViewHeight, 0, 0);
                startAnim();
                break;
            case RELEASE_TO_REFRESH://當前狀態為放開重新整理
                break;
            case PULL_TO_REFRESH://當前狀態為下拉重新整理
                startAnim();
                break;
            case REFRESHING://當前狀態為正在重新整理
                break;
            default:
                break;
        }
    }

    /** * 測量View * @param child */
    private void measureView(View child) {
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
        int lpHeight = p.height;
        int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
                    MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0,
                    MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
    }

    /** * 開啟動畫 */
    public void startAnim(){
        ivBack1.startAnimation(backAnimation1);
        ivBack2.startAnimation(backAnimation2);
        ivSun.startAnimation(sunAnimation);
        ivWheel1.startAnimation(wheelAnimation);
        ivWheel2.startAnimation(wheelAnimation);
    }

    /** * 關閉動畫 */
    public void stopAnim(){
        ivBack1.clearAnimation();
        ivBack2.clearAnimation();
        ivSun.clearAnimation();
        ivWheel1.clearAnimation();
        ivWheel2.clearAnimation();
    }
}

仿美團載入中小人

這裡寫圖片描述
美團的下拉重新整理分為三個狀態:
第一個狀態為下拉重新整理狀態(pull to refresh),在這個狀態下是一個綠色的橢圓隨著下拉的距離動態改變其大小。
第二個部分為放開重新整理狀態(release to refresh),在這個狀態下是一個幀動畫,效果為從躺著變為站起來的動畫。
第三個部分為重新整理狀態(refreshing),在這個狀態下也是一個幀動畫,是搖頭的動畫。
其中第二和第三個狀態很簡單,就是兩個幀動畫,第一個狀態我們可以用自定義View來實現。
第一個狀態的實現:
我們的思路是:當前這個橢圓形有一個進度值,這個進度值從0變為1,然後對這個橢圓形進行縮放,我們可以使用自定義View來實現這個效果。
MeiTuanRefreshFirstStepView

public class MeiTuanRefreshFirstStepView extends View{

    private Bitmap initialBitmap;
    private int measuredWidth;
    private int measuredHeight;
    private Bitmap endBitmap;
    private float mCurrentProgress;
    private Bitmap scaledBitmap;

    public MeiTuanRefreshFirstStepView(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    public MeiTuanRefreshFirstStepView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MeiTuanRefreshFirstStepView(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context) {
        //這個就是那個橢圓形圖片
        initialBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pull_image));
        //這個是第二個狀態娃娃的圖片,之所以要這張圖片,是因為第二個狀態和第三個狀態的圖片的大小是一致的,而第一階段
        //橢圓形圖片的大小與第二階段和第三階段不一致,因此我們需要根據這張圖片來決定第一張圖片的寬高,來保證
        //第一階段和第二、三階段的View的寬高一致
        endBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pull_end_image_frame_05));
    }

    /**
     * 重寫onMeasure方法主要是設定wrap_content時 View的大小
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //根據設定的寬度來計算高度  設定為符合第二階段娃娃圖片的寬高比例
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureWidth(widthMeasureSpec)*endBitmap.getHeight()/endBitmap.getWidth());
    }

    /**
     * 當wrap_content的時候,寬度即為第二階段娃娃圖片的寬度
     * @param widMeasureSpec
     * @return
     */
    private int measureWidth(int widMeasureSpec){
        int result = 0;
        int size = MeasureSpec.getSize(widMeasureSpec);
        int mode = MeasureSpec.getMode(widMeasureSpec);
        if (mode == MeasureSpec.EXACTLY){
            result = size;
        }else{
            result = endBitmap.getWidth();
            if (mode == MeasureSpec.AT_MOST){
                result = Math.min(result,size);
            }
        }
        return result;
        }

    /**
     * 在onLayout裡面獲得測量後View的寬高
     * @param changed
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        measuredWidth = getMeasuredWidth();
        measuredHeight = getMeasuredHeight();
        //根據第二階段娃娃寬高  給橢圓形圖片進行等比例的縮放
        scaledBitmap = Bitmap.createScaledBitmap(initialBitmap, measuredWidth,measuredWidth*initialBitmap.getHeight()/initialBitmap.getWidth(), true);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //這個方法是對畫布進行縮放,從而達到橢圓形圖片的縮放,第一個引數為寬度縮放比例,第二個引數為高度縮放比例,
        canvas.scale(mCurrentProgress, mCurrentProgress, measuredWidth/2, measuredHeight/2);
        //將等比例縮放後的橢圓形畫在畫布上面
        canvas.drawBitmap(scaledBitmap,0,measuredHeight/4,null);

    }

    /**
     * 設定縮放比例,從0到1  0為最小 1為最大
     * @param currentProgress
     */
    public void setCurrentProgress(float currentProgress){
        mCurrentProgress = currentProgress;
    }


}

第二個狀態的實現:
第二個狀態是一個幀動畫,我們為了保證View大小的統一,我們也進行自定義View,這個自定義View很簡單,只是為了和第一階段View的寬高保證一致即可。

public class MeiTuanRefreshSecondStepView extends View{

    private Bitmap endBitmap;

    public MeiTuanRefreshSecondStepView(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public MeiTuanRefreshSecondStepView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MeiTuanRefreshSecondStepView(Context context) {
        super(context);
        init();
    }

    private void init() {
        endBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pull_end_image_frame_05));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureWidth(widthMeasureSpec)*endBitmap.getHeight()/endBitmap.getWidth());
    }

    private int measureWidth(int widthMeasureSpec){
        int result = 0;
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        }else {
            result = endBitmap.getWidth();
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }
}

我們用xml定義一組幀動畫

<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"     android:oneshot="true" >      
<item android:drawable="@drawable/pull_end_image_frame_01" android:duration="100"/>     <item android:drawable="@drawable/pull_end_image_frame_02" android:duration="100"/>     <item android:drawable="@drawable/pull_end_image_frame_03" android:duration="100"/>     <item android:drawable="@drawable/pull_end_image_frame_04" android:duration="100"/>     <item android:drawable="@drawable/pull_end_image_frame_05" android:duration="100"/>  </animation-list>

幀動畫的啟動和停止方式:

mSecondView = (MeiTuanRefreshSecondStepView) headerView.findViewById(R.id.second_view); mSecondView.setBackgroundResource(R.drawable.pull_to_refresh_second_anim); secondAnim = (AnimationDrawable) mSecondView.getBackground(); //啟動 secondAnim.start(); //停止 secondAnim.stop();

第三個狀態的實現:
和第二個狀態同理,我們也通過自定義View來確保三個狀態的View的寬高保持一致。

public class MeiTuanRefreshThirdStepView extends View{

    private Bitmap endBitmap;

    public MeiTuanRefreshThirdStepView(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public MeiTuanRefreshThirdStepView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MeiTuanRefreshThirdStepView(Context context) {
        super(context);
        init();
    }

    private void init() {
        endBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pull_end_image_frame_05));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureWidth(widthMeasureSpec)*endBitmap.getHeight()/endBitmap.getWidth());
    }

    private int measureWidth(int widthMeasureSpec){
        int result = 0;
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        }else {
            result = endBitmap.getWidth();
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

}

我們在xml中定義一組幀動畫:

<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" > <item android:drawable="@drawable/refreshing_image_frame_01" android:duration="100"/> <item android:drawable="@drawable/refreshing_image_frame_02" android:duration="100"/> <item android:drawable="@drawable/refreshing_image_frame_03" android:duration="100"/> <item android:drawable="@drawable/refreshing_image_frame_04" android:duration="100"/> <item android:drawable="@drawable/refreshing_image_frame_05" android:duration="100"/> <item android:drawable="@drawable/refreshing_image_frame_06" android:duration="100"/> <item android:drawable="@drawable/refreshing_image_frame_07" android:duration="100"/> <item android:drawable="@drawable/refreshing_image_frame_08" android:duration="100"/> </animation-list>

下拉重新整理的實現:
首先我們要定義好幾個狀態,下拉重新整理有這樣幾個狀態:
DONE:隱藏的狀態
PULL_TO_REFRESH:下拉重新整理的狀態
RELEASE_TO_REFRESH:鬆開重新整理的狀態
REFRESHING:正在重新整理的狀態

public class MeiTuanListView extends ListView implements AbsListView.OnScrollListener{
    private static final int DONE = 0;
    private static final int PULL_TO_REFRESH = 1;
    private static final int RELEASE_TO_REFRESH = 2;
    private static final int REFRESHING = 3;
    private static final int RATIO = 3;
    private LinearLayout headerView;
    private int headerViewHeight;
    private float startY;
    private float offsetY;
    private TextView tv_pull_to_refresh;
    private OnMeiTuanRefreshListener mOnRefreshListener;
    private int state;
    private int mFirstVisibleItem;
    private boolean isRecord;
    private boolean isEnd;
    private boolean isRefreable;
    private FrameLayout mAnimContainer;
    private Animation animation;
    private SimpleDateFormat format;
    private MeiTuanRefreshFirstStepView mFirstView;
    private MeiTuanRefreshSecondStepView mSecondView;
    private AnimationDrawable secondAnim;
    private MeiTuanRefreshThirdStepView mThirdView;
    private AnimationDrawable thirdAnim;

    public MeiTuanListView(Context context) {
        super(context);
        init(context);
    }

    public MeiTuanListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MeiTuanListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public interface OnMeiTuanRefreshListener{
        void onRefresh();
    }

    /**
     * 回撥介面,想實現下拉重新整理的listview實現此介面
     * @param onRefreshListener
     */
    public void setOnMeiTuanRefreshListener(OnMeiTuanRefreshListener onRefreshListener){
        mOnRefreshListener = onRefreshListener;
        isRefreable = true;
    }

    /**
     * 重新整理完畢,從主執行緒傳送過來,並且改變headerView的狀態和文字動畫資訊
     */
    public void setOnRefreshComplete(){
        //一定要將isEnd設定為true,以便於下次的下拉重新整理
        isEnd = true;
        state = DONE;

        changeHeaderByState(state);
    }

    private void init(Context context) {
        setOverScrollMode(View.OVER_SCROLL_NEVER);
        setOnScrollListener(this);

        headerView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.meituan_item, null, false);
        mFirstView = (MeiTuanRefreshFirstStepView) headerView.findViewById(R.id.first_view);
        tv_pull_to_refresh = (TextView) headerView.findViewById(R.id.tv_pull_to_refresh);
        mSecondView = (MeiTuanRefreshSecondStepView) headerView.findViewById(R.id.second_view);
        mSecondView.setBackgroundResource(R.drawable.pull_to_refresh_second_anim);
        secondAnim = (AnimationDrawable) mSecondView.getBackground();
        mThirdView = (MeiTuanRefreshThirdStepView) headerView.findViewById(R.id.third_view);
        mThirdView.setBackgroundResource(R.drawable.pull_to_refresh_third_anim);
        thirdAnim = (AnimationDrawable) mThirdView.getBackground();

        measureView(headerView);
        addHeaderView(headerView);
        headerViewHeight = headerView.getMeasuredHeight();
        headerView.setPadding(0, -headerViewHeight, 0, 0);
        Log.i("zhangqi","headerViewHeight="+headerViewHeight);

        state = DONE;
        isEnd = true;
        isRefreable = 
            
           

相關推薦

APP實用開發定義載入動畫

彷百度外賣動畫 動畫 我們先來看看Android中的動畫吧: Android中的動畫分為三種: Tween動畫,這一類的動畫提供了旋轉、平移、縮放等效果。 Alpha – 淡入淡出 Scale – 縮放效果 Roate – 旋轉效果 Tran

實現下拉重新整理,上拉載入定義各種動畫

一、使用說明 1、UltimateRefreshView 支援ListView,GridView,ScrollView,WebVIew,RecyclerView(只支援LinearLayoutManager). 2、佈局使用: 1 2 3 4 5 6 7 8 9 1

iOS 定義載入等待動畫

一般來說,我們的專案中請求網路資料是一個比較耗時的操作,在請求的過程中如果給使用者只展示空白的頁面或者預設的頁面,難免顯得有些單調,這個時候我們可以新增一個指示動畫,開始請求的時候執行動畫,資料請求下來了停止動畫,這樣使用者體驗會好一些。下面開始自定義我們自己的載入指示動畫

iOS開發定義載入等待框(MBProgressHUD)

原文地址:http://blog.csdn.net/ryantang03/article/details/7877120 MBProgressHUD是一個開源專案,實現了很多種樣式的提示框,使用上簡單、方便,並且可以對顯示的內容進行自定義,功能很強大,很多專案中都有

andbase框架實現上拉載入,下拉重新整理和定義旋轉動畫的方式

1、今天做列表時,需求上需要做一個下拉重新整理,下拉載入更多的功能,就上網找了一些例子,由於我原來用的就是andbase框架,就還是用它原來寫的了。其中同事給我推薦另一個框架BGARefreshLayout-Android,下載地址https://github.com/bin

小程序開發 定義轉發

高版本 獲取數據 目標 www. wsh message sce index 表示 請確認測試手機微信版本為最高版本 1,wxml (主要) <button open-type="share">分享</button> //<button o

Python web開發——定義userprofile(用戶描述)

描述 刪除 生成 需要 username ive image .com bsp 1、新建一個APP 2、查看數據庫中系統給我們提供的默認的users的字段含義 ID: 是主鍵,用戶的ID passWord:密碼 last_login : 最後一次登錄的時間 is

Kivy 中文教程 實例入門 簡易畫板 (Simple Paint App):1. 定義窗口部件 (widget)

mage 動作 顯示 lac one 參數 sublime elf 入門 1. 框架代碼 用 PyCharm 新建一個名為 SimplePaintApp 的項目,然後新建一個名為 simple_paint_app.py 的 Python 源文件, 在代碼編輯器中,輸入以下框

java開發----定義對象,重寫equals方法

get ava string AI 擴展 hash date 方法重寫 int javaweb開發中,用到了好多自定義對象,這時候如果不重寫equals方法,很多時候都會返回false, 因此我們必須習慣重寫這個方法。 重點: 1.equals比較倆對象時比較的是對象

Android定義Transition動畫

本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。 曾經(或者現在)很多人說起Android和iOS都會拿Android的UI設計來開黑, “你看看人家iOS的設計, 再來看看Android的, 差距怎麼就這麼大呢?”, 對於這種說辭

非常實用定義佈局,定義顯示時長的頂部toast

最近在工作中需要彈出頂部toast且顯示時間不固定。從而寫了下面的一個模擬toast的動畫: 先看動畫: public void isShowToast(final boolean isShow,View mToastV) { final int marinTop = 0;//距離頂

小程式 定義 迴圈 動畫

本文出自: http://blog.csdn.net/wyk304443164 小程式 animation 建立的animation 不能迴圈,所以我們直接使用 css的動畫,真搞不懂 為啥小程式還要搞一套這種動畫出來。。 <view class="

Android開發-定義Dialog

下面是效果圖 定義一個MyDialog.java工具類繼承Dialog類 具體程式碼如下: /** * Created by WW on 2018/9/12. */ public abstract class MyDialog extends Dialog{ pr

[Android]定義開機動畫實踐 Android定製:修改開機啟動畫面

關於開機動畫的修改,可以參考 Android定製:修改開機啟動畫面 開機動畫主要是載入圖片,就像動畫片一樣。 在製作bootanimation.zip檔案的時候,需要注意 應該採用“儲存”(store)的方式去打包,不然系統會識別不了圖片資源 下面是一個動畫修改測試,我們也

iOS開發-定義後臺顯示圖片 iOS7-Background Fetch的應用

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

android憤怒小鳥遊戲、定義View、掌上餐廳App、OpenGL定義氣泡、抖音電影濾鏡效果等原始碼

Android精選原始碼 精練的範圍選擇器,範圍和單位可以自定義 自定義View做的小鳥遊戲 android popwindow選擇商品規格顏色尺寸效果原始碼 實現Android帶有鋸齒背景的優惠樣式原始碼 android充值頁面效果原始碼 使用

elasticsearch 外掛開發-定義分詞方法

自定義elasticsearch外掛實現 1 外掛專案結構 這是一個傳統的maven專案結構,主要是多了一些外掛需要的的目錄和檔案 plugin.xml和plugin-descriptor.properties這兩個是外掛的主要配置和描述 pom.xml裡面也

微信小程序開發---定義組件

開發 們的 函數 toast com 配置 微信小程序開發 sin efi 開發者可以將頁面內的功能模塊抽象成自定義組件,以便在不同的頁面中重復使用;也可以將復雜的頁面拆分成多個低耦合的模塊,有助於代碼維護。自定義組件在使用時與基礎組件非常相似。 創建自定義組件 類似於頁面

移動開發-----定義View(圓環)

import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.grap

移動開發----定義樣式 Dialog

1、基本用法: CustomDialog.Builder customBuilder = new CustomDialog.Builder(context); customBuilder.setTitle(title) .setMess