android下拉刷新精彩動畫

之前看到一種下拉刷新的效果,與以往的下拉效果都不一樣,大多數下拉刷新都是一個圓形進度條在旋轉,而這個下拉刷新則是一個不斷填充的效果。本以爲這是個自定義View,後來反編譯慕課網的app後提取資源的時候看到好多的圖片,那大概慕課網app內部的實現應該是幀動畫達到這種效果。而當我看到這種效果的時候,由於前段時間在學自定義控件,所以本能的反應則是自定義的。首先我們看下慕課網的效果。如下圖

package cn.edu.zafu.view;  

import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.Bitmap.Config;  
import android.graphics.BitmapFactory;  
import android.graphics.Canvas;  
import android.graphics.Color;  
import android.graphics.Paint;  
import android.graphics.Path;  
import android.graphics.PorterDuff;  
import android.graphics.PorterDuffXfermode;  
import android.util.AttributeSet;  
import android.view.View;  

/** 
 * @author lizhangqu 
 *  
 *         2015-3-5 
 */  
public class CustomView extends View {  
    private PorterDuffXfermode porterDuffXfermode;// Xfermode  
    private Paint paint;// 畫筆  
    private Bitmap bitmap;// 源圖片  
    private int width, height;// 控件寬高  
    private Path path;// 畫貝塞爾曲線需要用到  
    private Canvas mCanvas;// 在該畫布上繪製目標圖片  
    private Bitmap bg;// 目標圖片  

    private float controlX, controlY;// 貝塞爾曲線控制點,使用三階貝塞爾曲線曲線,需要兩個控制點,兩個控制點都在該變量基礎上生成  
    private float waveY;// 上升的高度  

    private boolean isIncrease;// 用於控制控制點水平移動  

    private boolean isReflesh = true;// 是否刷新併產生填充效果,默認爲true  

    /** 
     * @return 是否刷新 
     */  
    public boolean isReflesh() {  
        return isReflesh;  
    }  

    /** 
     * 提供接口設置刷新 
     *  
     * @param isReflesh 
     */  
    public void setReflesh(boolean isReflesh) {  
        this.isReflesh = isReflesh;  
        //重繪  
        postInvalidate();  
    }  

    /** 
     * @param context 
     */  
    public CustomView(Context context) {  
        this(context, null);  
    }  

    /** 
     * @param context 
     * @param attrs 
     */  
    public CustomView(Context context, AttributeSet attrs) {  
        this(context, attrs, 0);  
    }  

    /** 
     * @param context 
     * @param attrs 
     * @param defStyle 
     */  
    public CustomView(Context context, AttributeSet attrs, int defStyle) {  
        super(context, attrs, defStyle);  
        init();  
    }  

    /** 
     * 初始化變量 
     */  
    private void init() {  
        // 初始化畫筆  
        paint = new Paint();  
        paint.setAntiAlias(true);  
        paint.setDither(true);  
        paint.setStyle(Paint.Style.FILL);  
        paint.setColor(Color.parseColor("#ffc9394a"));  
        // 獲得資源文件  
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mooc);  
        // 設置寬高爲圖片的寬高  
        width = bitmap.getWidth();  
        height = bitmap.getHeight();  

        // 初始狀態值  
        waveY = 7 / 8F * height;  
        controlY = 17 / 16F * height;  

        // 初始化Xfermode  
        porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);  
        // 初始化path  
        path = new Path();  
        // 初始化畫布  
        mCanvas = new Canvas();  
        // 創建bitmap  
        bg = Bitmap.createBitmap(width, height, Config.ARGB_8888);  
        // 將新建的bitmap注入畫布  
        mCanvas.setBitmap(bg);  

    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        // 畫目標圖,存在bg上  
        drawTargetBitmap();  
        // 將目標圖繪製在當前畫布上,起點爲左邊距,上邊距的交點  
        canvas.drawBitmap(bg, getPaddingLeft(), getPaddingTop(), null);  
        if (isReflesh) {  
            // 重繪,使用boolean變量isReflesh進行控制,並對外提供訪問的接口,默認爲true且刷新  
            invalidate();  
        }  
    }  

    private void drawTargetBitmap() {  
        // 重置path  
        path.reset();  
        // 擦除像素  
        bg.eraseColor(Color.parseColor("#00ffffff"));  

        // 當控制點的x座標大於或等於終點x座標時更改標識值  
        if (controlX >= width + 1 / 2 * width) {  
            isIncrease = false;  
        }  
        // 當控制點的x座標小於或等於起點x座標時更改標識值  
        else if (controlX <= -1 / 2 * width) {  
            isIncrease = true;  
        }  

        // 根據標識值判斷當前的控制點x座標是該加還是減  
        controlX = isIncrease ? controlX + 10 : controlX - 10;  
        if (controlY >= 0) {  
            // 波浪上移  
            controlY -= 1;  
            waveY -= 1;  
        } else {  
            // 超出則重置位置  
            waveY = 7 / 8F * height;  
            controlY = 17 / 16F * height;  
        }  

        // 貝塞爾曲線的生成  
        path.moveTo(0, waveY);  
        // 兩個控制點通過controlX,controlY生成  
        path.cubicTo(controlX / 2, waveY - (controlY - waveY),  
                (controlX + width) / 2, controlY, width, waveY);  
        // 與下下邊界閉合  
        path.lineTo(width, height);  
        path.lineTo(0, height);  
        // 進行閉合  
        path.close();  

        // 以上畫貝塞爾曲線代碼參考自愛哥博客  
        // http://blog.csdn.net/aigestudio/article/details/41960507  

        mCanvas.drawBitmap(bitmap, 0, 0, paint);// 畫慕課網logo  
        paint.setXfermode(porterDuffXfermode);// 設置Xfermode  
        mCanvas.drawPath(path, paint);// 畫三階貝塞爾曲線  
        paint.setXfermode(null);// 重置Xfermode  
    }  

    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        // 獲得寬高測量模式和大小  
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
        // 保存測量結果  
        int width, height;  

        if (widthMode == MeasureSpec.EXACTLY) {  
            // 寬度  
            width = widthSize;  
        } else {  
            // 寬度加左右內邊距  
            width = this.width + getPaddingLeft() + getPaddingRight();  
            ;  
            if (widthMode == MeasureSpec.AT_MOST) {  
                // 取小的那個  
                width = Math.min(width, widthSize);  
            }  

        }  

        if (heightMode == MeasureSpec.EXACTLY) {  
            // 高度  
            height = heightSize;  
        } else {  
            // 高度加左右內邊距  
            height = this.height + getPaddingTop() + getPaddingBottom();  
            ;  
            if (heightMode == MeasureSpec.AT_MOST) {  
                // 取小的那個  
                height = Math.min(height, heightSize);  
            }  

        }  
        // 設置高度寬度爲logo寬度和高度,實際開發中應該判斷MeasureSpec的模式,進行對應的邏輯處理,這裏做了簡單的判斷測量  
        setMeasuredDimension(width, height);  

    }  

}  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@+id/ll"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
    <cn.edu.zafu.view.CustomView   
        android:id="@+id/cv"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:padding="20dp"  
        android:layout_centerInParent="true"  
        android:background="#0000ff"  
        />  
</RelativeLayout>  

如果要停止其不斷填充的效果,通過函數setReflesh設置isReflesh變量爲false即可。
整個實現過程還是相對簡單的,基本上註釋都講的很清楚了,這裏也不再重複了,文章中涉及到的兩個知識點(圖形的混合模式和貝塞爾曲線)的相關內容參考下面兩篇文章
圖形混合模式 http://blog.csdn.net/aigestudio/article/details/41316141
貝塞爾曲線 http://blog.csdn.net/aigestudio/article/details/41960507
都是愛哥的文章,個人覺得寫得很細。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章