Android RippleEffect波纹效果,重写View

      Android RippleEffect波纹效果,重写View

      在前一篇中介绍了通过重写ViewGroup实现RippleEffect波纹效果,这里再介绍介绍一种通过重写View的方式实现RippleEffect波纹效果的方式,当然,顺便还介绍 通过重写Button或者ImagView,实现点击变色效果的方式,不再写 selector了,直接在XMl的属性中就可以设置。这是fork的Git上的一个项目,有几个小Bug,自己修复了一下。Git地址:https://github.com/myjoybar/AndroidUIView,Git上的是Android studio版本的,这里展示的效果是Eclipse版的。

      CardView中,使用 android:foreground="?android:attr/selectableItemBackground",可以使CardView点击产生触摸点向外扩散波纹的效果,网易新闻客户端item的点击效果就是用CardView的这个属性实现的。关于android Recyclerview 和 CardView,以后介绍。


      自定义属性:

   <declare-styleable name="UIButton">
        <attr name="color_pressed" format="color"/>
        <attr name="color_unpressed" format="color"/>
        <attr name="ripple_color" format="color"/>
        <attr name="ripple_alpha" format="integer"/>
        <attr name="ripple_duration" format="integer"/>
        <attr name="alpha_pressed" format="integer"/>
        <attr name="radius" format="dimension"/>
        <attr name="shape_type" format="enum">
            <enum name="round" value="0"/>
            <enum name="rectangle" value="1"/>
        </attr>
    </declare-styleable>

  • color_unpressed: 未按下填充的颜色。
  • color_pressed:按下填充的颜色。
  • alpha_pressed:给color_pressed设置透明度,按下的效果就是color_pressed和color_unpressed重叠产生的。
  • radius:绘制圆角矩形时x方向上的圆角半径和y方向上的圆角半径。
  • shape_type:绘制圆drawCircle或者绘制圆角矩形drawRoundRect。
  • ripple_color:RippleEffect波纹颜色。
  • ripple_alpha:Ripple透明度。
  • radius:Ripple的半径。

      在UIBaseButton中,主要是绘制以color_unpressed颜色绘制圆或者绘制圆角矩形:

public class UIBaseButton extends Button {

    protected int WIDTH;
    protected int HEIGHT;
    protected Paint mBackgroundPaint;
    protected int mShapeType;
    protected int mRadius;

    public UIBaseButton(Context context) {
        this(context, null);
    }

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

    public UIBaseButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

   
    
    protected void init(final Context context, final AttributeSet attrs) {
        if (isInEditMode()) return;
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.UIButton);
        mShapeType = typedArray.getInt(R.styleable.UIButton_shape_type, 1);
        mRadius = typedArray.getDimensionPixelSize(R.styleable.UIButton_radius, getResources().getDimensionPixelSize(R.dimen.ui_radius));
        int unpressedColor = typedArray.getColor(R.styleable.UIButton_color_unpressed, Color.TRANSPARENT);
        typedArray.recycle();

        mBackgroundPaint = new Paint();
        mBackgroundPaint.setStyle(Paint.Style.FILL);
        mBackgroundPaint.setAlpha(Color.alpha(unpressedColor));
        mBackgroundPaint.setColor(unpressedColor);
        mBackgroundPaint.setAntiAlias(true);

        this.setWillNotDraw(false);
        this.setDrawingCacheEnabled(true);
        this.setClickable(true);
        if (unpressedColor != Color.TRANSPARENT)
            this.setBackgroundColor(Color.TRANSPARENT);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        WIDTH = w;
        HEIGHT = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBackgroundPaint == null) {
            super.onDraw(canvas);
            return;
        }
        if (mShapeType == 0) {
            canvas.drawCircle(WIDTH / 2, HEIGHT / 2, WIDTH / 2, mBackgroundPaint);
        } else {
            RectF rectF = new RectF();
            rectF.set(0, 0, WIDTH, HEIGHT);
            canvas.drawRoundRect(rectF, mRadius, mRadius, mBackgroundPaint);
        }
        super.onDraw(canvas);
    }
}
      简单介绍一下,不熟悉 Paint,Canvas,path的童鞋去补一下。
Paint.Style.FILL:填充内部,
setAntiAlias(true):抗锯齿功能
setWillNotDraw(false):保证View的onDraw函数被调用
setDrawingCacheEnabled(true):提高绘图速度
如果 drakeet:shape_type="round",那么就以View的中心为圆心,以View的宽度的 1/ 2为半径绘制一个圆。如果drakeet:shape_type="rectangle",就根据设定的四个点绘制矩形。

      UIRippleButton:

<pre name="code" class="java">public class UIRippleButton extends UIBaseButton {

	private int mRoundRadius;
	private int mRippleColor;
	private int mRippleDuration;
	private int mRippleRadius;
	private float pointX, pointY;

	private Paint mRipplePaint;
	private RectF mRectF;
	private Path mPath;
	private Timer mTimer;
	private TimerTask mTask;
	private Handler mHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			if (msg.what == MSG_DRAW_COMPLETE) {
				invalidate();
			}
		}
	};

	private int mRippleAlpha;
	// private final static int HALF_ALPHA = 127;
	private final static int RIPPLR_ALPHE = 47;
	private final static int MSG_DRAW_COMPLETE = 101;

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

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

	public UIRippleButton(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		init(context, attrs);
	}

	protected void init(final Context context, final AttributeSet attrs) {
		super.init(context, attrs);
		if (isInEditMode()) {
			return;
		}
		final TypedArray typedArray = context.obtainStyledAttributes(attrs,
				R.styleable.UIButton);
		mRippleColor = typedArray.getColor(R.styleable.UIButton_ripple_color,
				getResources().getColor(R.color.ripple_color));
		mRippleAlpha = typedArray.getInteger(R.styleable.UIButton_ripple_alpha,
				RIPPLR_ALPHE);
		mRippleDuration = typedArray.getInteger(
				R.styleable.UIButton_ripple_duration, 1000);
		mShapeType = typedArray.getInt(R.styleable.UIButton_shape_type, 1);
		mRoundRadius = typedArray.getDimensionPixelSize(
				R.styleable.UIButton_radius, getResources()
						.getDimensionPixelSize(R.dimen.ui_radius));
		typedArray.recycle();
		mRipplePaint = new Paint();
		mRipplePaint.setColor(mRippleColor);
		mRipplePaint.setAlpha(mRippleAlpha);
		mRipplePaint.setStyle(Paint.Style.FILL);
		mRipplePaint.setAntiAlias(true);
		mPath = new Path();
		mRectF = new RectF();
		pointY = pointX = -1;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		if (mRipplePaint == null) {
			return;
		}
		drawFillCircle(canvas);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			pointX = event.getX();
			pointY = event.getY();
			onStartDrawRipple();
		}
		return super.onTouchEvent(event);
	}

	/** Draw ripple effect */
	private void drawFillCircle(Canvas canvas) {
		if (canvas != null && pointX >= 0 && pointY >= 0) {
			int rbX = canvas.getWidth();
			int rbY = canvas.getHeight();
			float x_max = Math.max(pointX, Math.abs(rbX - pointX));
			float y_max = Math.max(pointY, Math.abs(rbY - pointY));
			float longDis = (float) Math.sqrt(x_max * x_max + y_max * y_max);
			if (mRippleRadius > longDis) {
				onCompleteDrawRipple();
				return;
			}
			final float drawSpeed = longDis / mRippleDuration * 35;
			mRippleRadius += drawSpeed;

			canvas.save();
			// canvas.translate(0, 0);//保持原点
			mPath.reset();
			canvas.clipPath(mPath);
			if (mShapeType == 0) {
				mPath.addCircle(rbX / 2, rbY / 2, WIDTH / 2, Path.Direction.CCW);
			} else {
				mRectF.set(0, 0, WIDTH, HEIGHT);
				mPath.addRoundRect(mRectF, mRoundRadius, mRoundRadius,
						Path.Direction.CCW);
			}
			canvas.clipPath(mPath, Region.Op.REPLACE);
			canvas.drawCircle(pointX, pointY, mRippleRadius, mRipplePaint);
			canvas.restore();
		}
	}

	/** Start draw ripple effect */
	private void onStartDrawRipple() {
		onCompleteDrawRipple();
		mTask = new TimerTask() {
			@Override
			public void run() {
				mHandler.sendEmptyMessage(MSG_DRAW_COMPLETE);
			}
		};
		mTimer = new Timer();
		mTimer.schedule(mTask, 0, 30);
	}

	/** Stop draw ripple effect */
	private void onCompleteDrawRipple() {
		mHandler.removeMessages(MSG_DRAW_COMPLETE);
		if (mTimer != null) {
			if (mTask != null) {
				mTask.cancel();
			}
			mTimer.cancel();
		}
		mRippleRadius = 0;
	}
}


 
       在onTouchEvent中,当event.getAction() == MotionEvent.ACTION_DOWN按下时,开启一个TimerTask定时器,每个30ms去invalidate()重绘一次View,在onStartDrawRipple()方法中首先执行了onCompleteDrawRipple(),是为了保证在RippleEffect波纹效果还未消失时,再次点击让上一次的效果消失,再执行此次的RippleEffect波纹效果。 
 

      在drawFillCircle中,为了保证RippleEffect波纹能够到达距离点击位置最远的Button边缘再消失,通过  

int rbX = canvas.getWidth();
			int rbY = canvas.getHeight();
			float x_max = Math.max(pointX, Math.abs(rbX - pointX));
			float y_max = Math.max(pointY, Math.abs(rbY - pointY));
			float longDis = (float) Math.sqrt(x_max * x_max + y_max * y_max);
计算出距离点击位置最远的边界点的距离,如果RippleEffect的半径大于这个值了,就调用onCompleteDrawRipple()让效果消失,逻辑和上一篇中的类似。

canvas.clipPath(mPath): makes the clip empty 
mPath.addCircle :添加一个圆形路径
mPath.addRoundRect:添加一个圆角矩形路径
Path.Direction.CCW :不闭合,顺时针
canvas.clipPath(mPath, Region.Op.REPLACE):裁剪出圆形或者矩形,
Region.Op.REPLACE指裁剪出的区域为我们的圆或者矩形
对这块不熟悉的话,去看一看ApiDemo中的那个Clipping 例子就明白了。
canvas.drawCircle(pointX, pointY, mRippleRadius, mRipplePaint):绘制出RippleEffect效果的圆。就这么简单的实现了RippleEffect波纹效果了,比上一篇的重写RelativeLayout简单一些。

      再来看看Button或者ImageView点击自带的变色效果。

public class UIButton extends UIBaseButton {

    private int COVER_ALPHA = 48;
    private Paint mPressedPaint;
    private int mPressedColor;

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

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

    public UIButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

   

    @Override
    protected void init(Context context, AttributeSet attrs) {
        super.init(context, attrs);
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.UIButton);
        COVER_ALPHA = typedArray.getInteger(R.styleable.UIButton_alpha_pressed, COVER_ALPHA);
        mPressedColor = typedArray.getColor(R.styleable.UIButton_color_pressed, getResources().getColor(R.color.color_pressed));     
        typedArray.recycle();
        mPressedPaint = new Paint();
        mPressedPaint.setStyle(Paint.Style.FILL);
        mPressedPaint.setColor(mPressedColor);
        mPressedPaint.setAlpha(0);
        mPressedPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mShapeType == 0) {
            canvas.drawCircle(WIDTH/2, HEIGHT/2, WIDTH/2.1038f, mPressedPaint);
        } else {
            RectF rectF = new RectF();
            rectF.set(0, 0, WIDTH, HEIGHT);
            canvas.drawRoundRect(rectF, mRadius, mRadius, mPressedPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPressedPaint.setAlpha(COVER_ALPHA);
                invalidate();
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mPressedPaint.setAlpha(0);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

      这里主要是绘制点击时的改变效果的颜色了,当MotionEvent.ACTION_DOWN按下时, 给color_pressed设置透明度,然后在通过 invalidate()刷新一下View就产生了点击变色的效果了。当MotionEvent.ACTION_UP抬起时:setAlpha(0),再invalidate()一下,效果消失。
在UIImageView中,因为已经有一张图片作为背景了,所以只需绘制点击后的效果,代码和UIButton类似,就不贴出来了。

DEMO下载地址http://download.csdn.net/detail/yalinfendou/8808663

猜你喜欢

转载自blog.csdn.net/yalinfendou/article/details/46503421