天天看點

android 自定義view之水波球

最近項目中需要實作動态水波球的效果,在網上看了一些都不太理想。就自己簡單寫了一個ProgressCircleView,期待大神拍磚!我這裡繼承的TextView,主要是友善在中間顯示目前值。

首先有圖有真相:

3.0及其之後版本的效果

android 自定義view之水波球

                  3.0之前版本的效果

android 自定義view之水波球

效果差異主要取決于如下的代碼,因為3.0以後google預設開啟了硬體加速導緻canvas.clipPath(path);沒有效果,是以在這裡隻能先區分處理。高版本的畫了一個前景來處理,如有更好的辦法還請指教。

protected void onDraw(Canvas canvas) {
		
		if (mHeight == 0) {
			// 初始化
			init();
			// 開始波動
			start();
		}
		
		if (android.os.Build.VERSION.SDK_INT > 10) {
			// 3.0 或更高版本
			drawForegroundCircle(canvas);
		} else {
			// 3.0以下版本
			clipCircle(canvas);
		}
		
		
		super.onDraw(canvas);
	}
           

主要的是這個方法用Path.rQuadTo去畫賽貝爾曲線(Path.rQuadTo和Path.quadTo的差別網上有很多這裡就不再贅述了):

/**
	 * 畫出水波線
	 * */
	private Path dealPath() {
		Path path = new Path();
		path.moveTo(mStartX, mStartY);
		
		for (int i = 0 ; i < mXs.length ; i++) {
			path.rQuadTo(mXs[i], mYs[i], mXs[i]<<1, 0);
			
		}
		path.rQuadTo(mXs[0], mYs[0], mXs[0]<<1, 0);
		path.rQuadTo(mXs[1], mYs[1], mXs[1]<<1, 0);
		path.lineTo(mWidth, mHeight);
		path.lineTo(0, mHeight);
		path.lineTo(mStartX, mStartY);
		path.close();
		
		return path;
	}
           

在Activity中使用的方法:

pcv0 = (ProgressCircleView) findViewById(R.id.progressCircleView4);
        
        pcv0.setProgress(0.5f);
        pcv0.setText("50%");
           

已下是ProgressCircleView的完整代碼:

/**
 * 水波紋進度球
 * */
public class ProgressCircleView extends TextView {
	
	private int mHeight;	//控件寬高
	private int mWidth;	//
	
	private int mStartX;	//起始位置坐标
	private int mStartY;	//
	
	private Paint mPaint;	// 邊框畫筆
	private Paint mQuadPaint;	// 水波紋畫筆
	private Paint mForePaint;	// 前景色畫筆
	
	private int[] mXs;
	private int[] mYs;
	
	private int mStrokeWidth = 4;	// 邊框寬度
	private int mTranslationUnit;	// 位移機關
	
	/** 水波紋總寬度 */
	private int mTotle;
	/** 目前進度值 */
	private float mProgress = 0.5f;
	/** 目前進度對應的顔色值 */
	private int mCurProgressColor = 0xFF00BE3C;
	/** 漸變顔色 */
	private int[] mProgressColors = new int[]{Color.RED, Color.BLUE, Color.CYAN, Color.GREEN};

	public ProgressCircleView(Context context) {
		super(context);
		
	}

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

	public ProgressCircleView(Context context, AttributeSet attrs,
			int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		
	}
	
	/**
	 * 設定進度值
	 * @param progress float 取值範圍:0.1 - 1.0
	 * */
	public void setProgress(float progress) {
		this.mProgress = 1 - progress;
		mCurProgressColor = interpRectColor(mProgressColors, progress); // 得到目前色值
		
		mHeight = 0;
		invalidate();
	}
	
	/**
	 * 初始化 得到一些資料
	 * */
	private void init() {
		mHeight = getHeight();
		mWidth = getWidth();
		
		mStartX = mStrokeWidth;
		mStartY = (int) (mHeight * mProgress);
		
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.parseColor("#EEEEEE"));
		mPaint.setStyle(Paint.Style.FILL);//設定為空心
		
		mQuadPaint = new Paint();
		mQuadPaint.setAntiAlias(true);
		mQuadPaint.setColor(mCurProgressColor);
		mQuadPaint.setStyle(Paint.Style.FILL);//設定實心
		
		mForePaint = new Paint();
		mForePaint.setAntiAlias(true);
		mForePaint.setColor(Color.WHITE);
		mForePaint.setStyle(Paint.Style.FILL);//設定實心

		mPaint.setStrokeWidth(mStrokeWidth);
		
		
		
		// 波紋坐标
		mTotle = 0;
		float scale = mProgress > 0.5f ? (1 - mProgress) * 2 : mProgress * 2;
		float[] xFs = new float[]{0.35f, 0.2f, 0.45f, 0.6f};
		float[] yFs = new float[]{0.15f, -0.12f, 0.2f, -0.25f};
		mXs = new int[xFs.length];
		mYs = new int[yFs.length];
		for (int i = 0 ; i < xFs.length ; i++) {
			mXs[i] = (int) (mWidth * xFs[i]);
			mYs[i] = (int) (mHeight * yFs[i] * scale);
			
			mTotle += mXs[i];
		}
		mTotle += mXs[0];
		mTotle += mXs[1];
		
		mTranslationUnit = mWidth / 50;
	}
	
	@SuppressLint("DrawAllocation") @Override
	protected void onDraw(Canvas canvas) {
		
		if (mHeight == 0) {
			if (mWidth == 0) {
				// 開始波動
				start();
			}
			// 初始化
			init();
		}
		
		if (android.os.Build.VERSION.SDK_INT > 10) {
			// 3.0 或更高版本
			drawForegroundCircle(canvas);
		} else {
			// 3.0以下版本
			clipCircle(canvas);
		}
		
		
		super.onDraw(canvas);
	}
	
	private void clipCircle(Canvas canvas) {
		canvas.save();
		Path path = new Path();
		path.addCircle(mWidth / 2, mHeight / 2, mWidth / 2 - (mStrokeWidth>>1), Direction.CW);
		canvas.clipPath(path);
		canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);
		canvas.drawPath(dealPath(), mQuadPaint);
//		canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2 - mStrokeWidth, mPaint);
		canvas.restore();
	}
	
	private void drawForegroundCircle(Canvas canvas) {
		Path path = new Path();
        float radius = mWidth / 2f;
        float panding = mStrokeWidth / 2f;

        path = new Path();
        path.moveTo(0, radius);
        path.lineTo(0, 0);
        path.lineTo(mWidth, 0);
        path.lineTo(mWidth, mHeight);
        path.lineTo(0, mHeight);
        path.lineTo(0, radius);
        //圓弧左邊中間起點是180,旋轉360度
        path.arcTo(new RectF(panding, panding, mWidth - panding, mHeight - panding), 180, -359, true);
        path.close();
        
        canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);
        canvas.drawPath(dealPath(), mQuadPaint);
//        canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2 - mStrokeWidth, mPaint);
        canvas.drawPath(path, mForePaint);
	}
	
	/**
	 * 畫出水波線
	 * */
	private Path dealPath() {
		Path path = new Path();
		path.moveTo(mStartX, mStartY);
		
		for (int i = 0 ; i < mXs.length ; i++) {
			path.rQuadTo(mXs[i], mYs[i], mXs[i]<<1, 0);
			
		}
		path.rQuadTo(mXs[0], mYs[0], mXs[0]<<1, 0);
		path.rQuadTo(mXs[1], mYs[1], mXs[1]<<1, 0);
		path.lineTo(mWidth, mHeight);
		path.lineTo(0, mHeight);
		path.lineTo(mStartX, mStartY);
		path.close();
		
		return path;
	}
	
	/**
	 * 開始波動效果
	 * */
	private void start() {
		mStartX -= mTranslationUnit;
		new Handler().postDelayed(new Runnable() {
			@Override
			public void run() {
				start();
			}
		}, 20);
		invalidate();
		if (-mStartX >= mTotle + mWidth + 1.2f * mStrokeWidth) {
			mStartX = mStrokeWidth;
		}
	}
	
	/** 
     * 擷取漸變顔色 
     * @param colors 
     * @param x 
     * @return 
     */  
    private int interpRectColor(int[] colors, float p) {
    	if (p == 0) {
    		return colors[0];
    	}
    	
    	int size = colors.length - 1;
    	float cp, up;
        int a, r, g, b, cur;
        up = 1f / size;
        cur = (int) (p / up);
        cp = (p - cur * up) / up;
        if (cp == 0) {
        	cur--;
        	cp = 1;
        }
        
        a = ave(Color.alpha(colors[cur]), Color.alpha(colors[cur+1]), cp);  
        r = ave(Color.red(colors[cur]), Color.red(colors[cur+1]), cp);  
        g = ave(Color.green(colors[cur]), Color.green(colors[cur+1]), cp);  
        b = ave(Color.blue(colors[cur]), Color.blue(colors[cur+1]), cp);  
        return Color.argb(a, r, g, b);  
    } 
	
	private int ave(int s, int d, float p) {  
        return s + Math.round(p * (d - s));  
    }
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		mHeight = 0;
		invalidate();
	}

}