慕課網app下拉刷新圖標填充效果的實現
來源:程序員人生 發布時間:2015-04-01 08:17:50 閱讀次數:3478次
之前看到1種下拉刷新的效果,與以往的下拉效果都不1樣,大多數下拉刷新都是1個圓形進度條在旋轉,而這個下拉刷新則是1個不斷填充的效果。本以為這是個自定義View,后來反編譯慕課網的app后提取資源的時候看到好多的圖片,那大概慕課網app內部的實現應當是幀動畫到達這類效果。而當我看到這類效果的時候,由于前段時間在學自定義控件,所以本能的反應則是自定義的。首先我們看下慕課網的效果。以下圖
而我的也實現了1個這個圖標填充的簡單版。以下圖
全部實現使用圖形的混合模式+貝塞爾曲線,貝塞爾曲線的繪制參考自愛哥的博客 貝塞爾曲線內容
資源文件就只有下面這個圖標,該圖標提取自慕課網app,然后對內部的火焰進行透明處理過

既然是自定義View,那就要繼承View,實現onDraw,onMeasure等方法,為了簡單起見,這里將控件的寬度高度直接設置為圖片的寬度和高度,而沒有去實現相應的邏輯去判斷MeasureSpec的模式是哪一個從而進行處理。
先貼代碼
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⑶⑸
*/
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;// 貝塞爾曲線控制點,使用3階貝塞爾曲線曲線,需要兩個控制點,兩個控制點都在該變量基礎上生成
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;
}
/**
* @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 <= ⑴ / 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);// 畫3階貝塞爾曲線
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 + getPaddingLeft() + getPaddingRight();
} else {
// 寬度加左右內邊距
width = this.width + getPaddingLeft() + getPaddingRight();
;
if (widthMode == MeasureSpec.AT_MOST) {
// 取小的那個
width = Math.min(width, widthSize);
}
}
if (heightMode == MeasureSpec.EXACTLY) {
// 高度加左右內邊距
height = heightSize + getPaddingTop() + getPaddingBottom();
} 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
都是愛哥的文章,個人覺得寫得很細。
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈