Android的遮罩效果就是把一张图片盖在另一张图片的上面,通过控制任意一张图片的显示百分比实现遮罩效果。下面我使用两张一样的图片来实现一个类似于 Android 的progressbar 的填充效果。使用遮罩效果来实现progressbar的效果的好处是,我们可以只改变图片就可以更改progress的进度填充效果,并且我们可以实现任意形式的填充效果,就比如横竖填充,扇形逆/顺时填充针等。
网上有很多介绍Android 遮罩效果的列子,但是都是横竖的填充效果,下面我来实现一个扇形填充效果,如下图:
我现在要做的就是用这两种图去实现一个progressbar效果.好了原来不解释了直接上代码吧:
一.Activity代码
package com.gplus.mask.test; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.ViewGroup.LayoutParams; import android.widget.LinearLayout; import android.widget.RelativeLayout; import com.gplus.mask.widget.MaskProgress; import com.gplus.mask.widget.MaskProgress.AnimateListener; public class GplusMask extends Activity{ float progressFromCode = 150; float progressFromXml = 150; MaskProgress maskProgressFromeCode; MaskProgress maskProgressFromeXml; private boolean isAnimateFinish = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); RelativeLayout parent = (RelativeLayout) findViewById(R.id.parent); maskProgressFromeCode = new MaskProgress(this); initialProgress(maskProgressFromeCode); RelativeLayout.LayoutParams rp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); parent.addView(maskProgressFromeCode, rp); maskProgressFromeCode.initial(); maskProgressFromeXml = (MaskProgress) findViewById(R.id.maskView); } private void initialProgress(MaskProgress maskProgress){ //设置最大值 maskProgress.setMax(300); //初始填充量为一半 //初始化填充progress时的填充动画时间,越大越慢 maskProgress.setTotaltime(3); //progress背景图 maskProgress.setBackgroundResId(R.drawable.untitled1); //progress填充内容图片 maskProgress.setContentResId(R.drawable.untitled2); //Progress开始的填充的位置360和0为圆最右、90圆最下、180为圆最右、270为圆最上(顺时针方向为正) maskProgress.setStartAngle(0); maskProgress.setAnimateListener(animateListener); //初始化时必须在setMax设置之后再设置setProgress maskProgress.setProgress(175); } Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); float newProgress = maskProgressFromeCode.getProgress() - 4; if(newProgress <= 0){//随机绘制效果 float max = (float) (Math.random() * 900 + 1000); float progress = (float) (max * Math.random()); maskProgressFromeCode.setMax(max); maskProgressFromeCode.setProgress(progress); maskProgressFromeCode.setTotaltime((float) (Math.random()*10)); maskProgressFromeCode.setStartAngle((float) (Math.random()*360)); maskProgressFromeCode.initial(); return; } maskProgressFromeCode.setProgress(newProgress); maskProgressFromeCode.updateProgress(); handler.sendEmptyMessageDelayed(0, 50); } }; AnimateListener animateListener = new AnimateListener() { @Override public void onAnimateFinish() { handler.sendEmptyMessageDelayed(0, 500); } }; }
二.activity布局文件main.xml
三.View的实现效果MaskProgress.java
package com.gplus.mask.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuffXfermode; import android.graphics.PorterDuff.Mode; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.View; /** * @author huangxin */ public class MaskProgress extends View{ /** 每次setProgress时进度条前进或者回退到所设的值时都会有一段动画。 * 该接口用于监听动画的完成,你应该设置监听器监听到动画完成后,才再一次调用 * setProgress方法 * */ public static interface AnimateListener{ public void onAnimateFinish(); } private float totalTime = 5;//s private final static int REFRESH = 10;//mills private float step; private float max = 360; private float currentProgress; private float destProgress = 0; private float realProgress = 0; private float oldRealProgress = 0; private int backgroundResId; private int contentResId; private float startAngle = 270; private Bitmap bg; private Bitmap ct; private Paint paint; private int radius; private int beginX; private int beginY; private int centerX; private int centerY; private RectF rectF; private PorterDuffXfermode srcIn; private double rate; boolean initialing = false; AnimateListener animateListener; public MaskProgress(Context context) { this(context, null); } public MaskProgress(Context context, AttributeSet attrs) { this(context, attrs, R.attr.maskProgressStyle); } public MaskProgress(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs, defStyle); } public void setAnimateListener(AnimateListener animateListener) { this.animateListener = animateListener; } public void setProgress(float destProgress) { if(destProgress > max) try { throw new Exception("progress can biger than max"); } catch (Exception e) { e.printStackTrace(); } this.destProgress = destProgress; oldRealProgress = realProgress; realProgress = (float) (destProgress * rate); } public float getProgress(){ return destProgress; } public void setTotaltime(float totalTime) { this.totalTime = totalTime; step = 360 / (totalTime * 1000 / REFRESH); } public static int getRefresh() { return REFRESH; } public void setMax(float max) { this.max = max; rate = 360 / max; } public void setStartAngle(float startAngle) { this.startAngle = startAngle; } public void setBackgroundResId(int backgroundResId) { this.backgroundResId = backgroundResId; bg = BitmapFactory.decodeResource(getResources(), backgroundResId); } public void setContentResId(int contentResId) { this.contentResId = contentResId; ct = BitmapFactory.decodeResource(getResources(), contentResId); } public void updateProgress(){ invalidate(); } /** 初始化,第一次给MaskProgress设值时,从没有填充到,填充到给定的值时 * 有一段动画 * */ public void initial(){ initialing = true; new CirculateUpdateThread().start(); } public float getMax() { return max; } private void init(Context context, AttributeSet attrs, int defStyle){ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.maskProgressBar, defStyle, 0); if (typedArray != null) { try { setMax(typedArray.getFloat(R.styleable.maskProgressBar_max, max)); setProgress(typedArray.getFloat(R.styleable.maskProgressBar_progress, destProgress)); setTotaltime(typedArray.getFloat(R.styleable.maskProgressBar_anim_time, totalTime)); setStartAngle(typedArray.getFloat(R.styleable.maskProgressBar_start_angle, startAngle)); setContentResId(typedArray.getResourceId(R.styleable.maskProgressBar_progress_content, R.drawable.untitled2)); setBackgroundResId(typedArray.getResourceId(R.styleable.maskProgressBar_progress_background, R.drawable.untitled1)); } finally { typedArray.recycle(); } } paint = new Paint(); paint.setDither(true); paint.setAntiAlias(true); rate = 360 / max; currentProgress = 0; realProgress = (float) (destProgress * rate); srcIn = new PorterDuffXfermode(Mode.SRC_IN); step = 360 / (totalTime * 1000 / REFRESH); bg = BitmapFactory.decodeResource(getResources(), backgroundResId); ct = BitmapFactory.decodeResource(getResources(), contentResId); Log.w("init", "max: " + max + "n" + "destProgress: " + destProgress +"n"+"totalTime: "+ totalTime+"n"+"startAngle: "+ startAngle); initialing = true; new CirculateUpdateThread().start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(bg, 0, (getHeight() - bg.getHeight()) / 2, paint); int rc = canvas.saveLayer(0, (getHeight() - bg.getHeight()) / 2, bg.getWidth(), (getHeight() + bg.getHeight()) / 2, null, Canvas.ALL_SAVE_FLAG); paint.setFilterBitmap(false); if(initialing){ canvas.drawArc(rectF, startAngle, currentProgress, true, paint); }else{ canvas.drawArc(rectF, startAngle, realProgress, true, paint); } paint.setXfermode(srcIn); canvas.drawBitmap(ct, 0, (getHeight() - ct.getHeight()) / 2, paint); paint.setXfermode(null); canvas.restoreToCount(rc); } public int[] getRectPosition(int progress){ int[] rect = new int[4]; rect[0] = beginX; rect[1] = beginY; rect[2] = (int)(centerX + radius * Math.cos(progress * Math.PI /180)); rect[3] = (int)(centerY + radius * Math.sin(progress * Math.PI /180)); Log.w("getRectPosition", "30: " + Math.sin(30 * Math.PI /180)); Log.w("getRectPosition", "X: " + rect[2] + " " + "Y: " + rect[3]); return rect; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); int tmp = w >= h ? h : w; radius = tmp / 2; beginX = w / 2; beginY = 0; centerX = tmp / 2; centerY = tmp / 2; Bitmap bg_ = resizeBitmap(bg, tmp, tmp); Bitmap ct_ = resizeBitmap(ct, tmp, tmp); rectF = new RectF(0, (getHeight() - bg_.getHeight()) / 2, bg_.getWidth(), (getHeight() + bg_.getHeight()) / 2); bg.recycle(); ct.recycle(); bg = bg_; ct = ct_; } private Bitmap resizeBitmap(Bitmap src, int w, int h){ int width = src.getWidth(); int height = src.getHeight(); int scaleWidht = w / width; int scaleHeight = h / height; Matrix matrix = new Matrix(); matrix.postScale(scaleWidht, scaleHeight); Bitmap result = Bitmap.createScaledBitmap(src, w, h, true); src = null; return result; } class CirculateUpdateThread extends Thread{ @Override public void run() { while(initialing){ postInvalidate(); if(currentProgress < realProgress){ currentProgress += step * rate; if(currentProgress > realProgress) currentProgress = realProgress; }else{ // new Thread(new Runnable() { // // @Override // public void run() { // while (true) { // postInvalidate(); // if (currentProgress > 0) { // currentProgress -= step * rate; // } else { // currentProgress = 0; // new CirculateUpdateThread().start(); // break; // } // try { // Thread.sleep(REFRESH); // } catch (Exception e) { // e.printStackTrace(); // } // } // } // }).start(); currentProgress = 0; initialing = false; if(animateListener != null) animateListener.onAnimateFinish(); } try{ Thread.sleep(REFRESH); }catch(Exception e){ e.printStackTrace(); } } } } }
四.该Veiw自定义的属性文件attrs.xml
效果图如下,上面小的是定义xml的,下面大的是从代码中添加的