之前看到一種下拉刷新的效果,與以往的下拉效果都不一樣,大多數下拉刷新都是一個圓形進度條在旋轉,而這個下拉刷新則是一個不斷填充的效果。本以爲這是個自定義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
都是愛哥的文章,個人覺得寫得很細。