天天看點

Android自定義控件之仿京東商城下拉重新整理

前面寫了4篇自定義控件的部落格,并且開通了一個專欄,把4篇文章添加到專欄中了,耐心等待部落格專欄的徽章出現,奈何等了幾周後還是沒有出現,後來發現至少需要5篇文章才能出現專欄徽章,于是有了這篇仿我大京東快遞小哥的下拉重新整理。

直接上圖先!

Android自定義控件之仿京東商城下拉重新整理

分析

這個下拉重新整理效果分為兩個部分:

step1:快遞小哥和快遞包裹的縮放效果,看上去就像是快遞小哥跑過來一手拿過快遞的樣子

step2:快遞小哥拿到包裹後,開啟暴走模式!玩命送快遞

PS:不得不贊一下京東的快遞,真的很快!

step1

好了馬屁拍完了,我們先來看一看第一部分的效果是怎麼實作的。首先快遞小哥和包裹是兩張圖檔

Android自定義控件之仿京東商城下拉重新整理
Android自定義控件之仿京東商城下拉重新整理

我們看到快遞小哥是從小變大的,快遞包裹也是從小變大的,是以我們要自定義一個控件,就起名為FirstStepView.java吧

public class FirstSetpView extends View{

    private Bitmap goods;
    private Bitmap people;
    private Bitmap peopleWithGoods;
    private int measuredWidth;
    private int measuredHeight;
    private float mCurrentProgress;
    private int mCurrentAlpha;
    private Paint mPaint;
    private Bitmap scaledPeople;
    private Bitmap scaledGoods;
    public FirstSetpView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

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

    public FirstSetpView(Context context) {
        super(context);
        init();
    }
    private void init(){
        //包裹bitmap
        goods = BitmapFactory.decodeResource(getResources(), R.mipmap.app_refresh_goods_0);
        //快遞小哥bitmap
        people = BitmapFactory.decodeResource(getResources(), R.mipmap.app_refresh_people_0);
        //這是後面動畫中的最後一張圖檔,拿這張圖檔的作用是用它的寬高來測量
        //我們這個自定義View的寬高
        peopleWithGoods = BitmapFactory.decodeResource(getResources(), R.mipmap.app_refresh_people_3);
        //來個畫筆,我們注意到快遞小哥和包裹都有一個漸變效果的,我們用
        //mPaint.setAlpha來實作這個漸變的效果
        mPaint = new Paint();
        //首先設定為完全透明
        mPaint.setAlpha();
    }

    /**
     * 測量方法
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }
    //測量寬度
    private int measureWidth(int widthMeasureSpec){
        int result = ;
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        if (MeasureSpec.EXACTLY == mode) {
            result = size;
        }else {
            result = peopleWithGoods.getWidth();
            if (MeasureSpec.AT_MOST == mode) {
                result = Math.min(result, size);
            }
        }
        return result;
    }
    //測量高度
    private int measureHeight(int heightMeasureSpec){
        int result = ;
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        if (MeasureSpec.EXACTLY == mode) {
            result = size;
        }else {
            result = peopleWithGoods.getHeight();
            if (MeasureSpec.AT_MOST == mode) {
                result = Math.min(result, size);
            }
        }
        return result;
    }
    //在這裡面拿到測量後的寬和高,w就是測量後的寬,h是測量後的高
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        measuredWidth = w;
        measuredHeight = h;
        //根據測量後的寬高來對快遞小哥做一個縮放
        scaledPeople = Bitmap.createScaledBitmap(people,measuredWidth,measuredHeight,true);
        //根據測量後的寬高來對快遞包裹做一個縮放
        scaledGoods = Bitmap.createScaledBitmap(goods, scaledPeople.getWidth()*/, scaledPeople.getHeight()/, true);
    }

    /**
     * 繪制方法
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //由于包裹和快遞小哥要分别來畫,是以使用save和restore方法
        //save
        //畫包裹
        //restore
        //save
        //畫小哥
        //restore
        canvas.save();
        canvas.scale(mCurrentProgress, mCurrentProgress ,  measuredWidth-scaledGoods.getWidth()/ , measuredHeight/);
        mPaint.setAlpha(mCurrentAlpha);
        canvas.drawBitmap(scaledGoods, measuredWidth-scaledGoods.getWidth(), measuredHeight/-scaledGoods.getHeight()/, mPaint);
        canvas.restore();
        canvas.save();
        canvas.scale(mCurrentProgress, mCurrentProgress ,  , measuredHeight/);
        mPaint.setAlpha(mCurrentAlpha);
        canvas.drawBitmap(scaledPeople, ,,mPaint);
        canvas.restore();
    }

    /**
     * 根據進度來對小哥和包裹進行縮放
     * @param currentProgress
     */
    public void setCurrentProgress(float currentProgress){
        this.mCurrentProgress = currentProgress;
        this.mCurrentAlpha = (int) (currentProgress*);
    }
}
           

還是老方法,我們寫完這個效果,就先來測試一下吧,還是用一個SeekBar來模拟一個進度值

public class MainActivity extends Activity {
    private SeekBar sb;
    private FirstSetpView mFirstStepView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sb = (SeekBar) findViewById(R.id.seekbar);
        mFirstStepView = (FirstSetpView) findViewById(R.id.firstview);
        sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress,
                    boolean fromUser) {
                float currentProgress = (float)seekBar.getProgress()/(float)seekBar.getMax();
                mFirstStepView.setCurrentProgress(currentProgress);
                mFirstStepView.invalidate();
            }
        });
    }


}
           
Android自定義控件之仿京東商城下拉重新整理

step2

好了,第一部分已經完成,接下來我們看第二部分,第二部分就簡單了,就是一個幀動畫,一共有3張圖檔

Android自定義控件之仿京東商城下拉重新整理
Android自定義控件之仿京東商城下拉重新整理
Android自定義控件之仿京東商城下拉重新整理

我們在res/drawable/下寫一個幀動畫second_step_animation.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="@mipmap/app_refresh_people_1" android:duration="70"/>
   <item android:drawable="@mipmap/app_refresh_people_2" android:duration="70"/>
   <item android:drawable="@mipmap/app_refresh_people_3" android:duration="70"/>

</animation-list>
           

我們為了保證第一階段和第二階段的View的寬和高一緻,我們還要再自定義一個View,不過我們不用去畫什麼,隻需要測量一下寬和高,讓他和FirstSetpView的寬高保持一緻

public class SecondStepView extends View{

    private Bitmap endBitmap;

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

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

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

    private void init(){
        //拿到幀動畫第三章圖檔,我們的FirstStepView的寬高也是根據這張圖檔來測量的,是以我們就能
        //保證兩個View的寬高一緻了
        endBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.app_refresh_people_3);
    }

    /**
     * 隻需要測量方法
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

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

}
           

好了,接下來再複習一下怎麼執行幀動畫

secondStepView = (SecondStepView) headerView.findViewById(R.id.second_step_view);
        secondStepView.setBackgroundResource(R.drawable.second_step_animation);
        secondAnimation = (AnimationDrawable) secondStepView.getBackground();
           

secondAnimation是一個AnimationDrawable對象,我們可以調用他的start方法開始幀動畫,調用stop結束幀動畫

secondAnimation.start();
secondAnimation.stop();
           

下拉重新整理的實作

這一部分我在Android自定義控件之仿美團下拉重新整理 中已經詳細分析過了,是以這裡就不再贅述了。

完整代碼

歡迎大家上我的GitHub上下載下傳源碼