滑动解锁 android Canvas自定义控件中硬件加速引起的canvas.clipPath问题

本文探讨了Android中硬件加速对canvas.clipPath操作的影响,导致裁剪失效的问题。在4.4以下设备上,硬件加速不支持clipPath,表现为裁剪失效。解决方法包括检查设备支持情况避免使用clipPath,或者直接禁用自定义View的硬件加速。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们知道,android中的图形都是矩形的。

要绘制一个如下的椭圆形控件(圆形图像,圆角listview,圆角按钮),我们需要裁剪画布


在4.4一下的设备上 使用canvas.clipPath裁剪画布以使得控件中绘制的内容能被不规则化


//裁剪画布
mContentRect.set(0, 0, getWidth(), getHeight());
mPath.addRoundRect(mContentRect, r/2,r/2, Path.Direction.CCW);
canvas.clipPath(mPath,android.graphics.Region.Op.REPLACE);
//绘制其他控件元素,灯泡 文字 背景


但是 在部分真是设备中,它的效果缺是这样的



裁剪失效,有没有?


问题在于canvas默认开启了硬件加速  

硬件加速具体的介绍见官方文档

http://developer.android.com/guide/topics/graphics/hardware-accel.html


下面是硬件加速不支持的API和SDK等级对照表




       
  API level
< 17 17 18
Support for large scale factors
drawText()
drawPosText()
drawTextOnPath()
Simple Shapes*
Complex Shapes*
drawPath()
Shadow layer

         
  API level
< 16 16 17 18
Canvas
drawBitmapMesh() (colors array)
drawPicture()
drawPosText()
drawTextOnPath()
drawVertices()
setDrawFilter()
clipPath()
clipRegion()
clipRect(Region.Op.XOR)
clipRect(Region.Op.Difference)
clipRect(Region.Op.ReverseDifference)
clipRect() with rotation/perspective
Paint
setAntiAlias() (for text)
setAntiAlias() (for lines)
setFilterBitmap()
setLinearText()
setMaskFilter()
setPathEffect() (for lines)
setRasterizer()
setShadowLayer() (other than text)
setStrokeCap() (for lines)
setStrokeCap() (for points)
setSubpixelText()
Xfermode
AvoidXfermode
PixelXorXfermode
PorterDuff.Mode.DARKEN (framebuffer)
PorterDuff.Mode.LIGHTEN (framebuffer)
PorterDuff.Mode.OVERLAY (framebuffer)
Shader
ComposeShader inside ComposeShader
Same type shaders inside ComposeShader
Local matrix on ComposeShader

clipPath 赫然就在其中

解决的办法的有2个;


1:依照上面的表不在不支持的设备上使用这个API,

2:对这个自定义的view 禁用硬件加速

setLayerType(View.LAYER_TYPE_SOFTWARE, null);




例子中的自定义控件完整代码如下
/**
 * slide to control
 */
public class SlideControllerView extends RelativeLayout {
	private static final int STATE_DRAGGING = 1;
	private String mExampleString;
	private int mExampleColor = Color.RED;
	private float mExampleDimension = 0;
	private Drawable mExampleDrawable;

	private TextPaint mTextPaint;
	private Paint mMaskPaint;
	private float mTextWidth;
	private float mTextHeight;
	private Path mPath;
	private Drawable mDrawable_button;
	private Drawable mDrawable_background_button;
	int COLOR_BLUE_LIGHT = 0xff3094ff;
	private ScrollerCompat mScroller;

	public SlideControllerView(Context context) {
		super(context);
		init(null, 0);
	}

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

	public SlideControllerView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(attrs, defStyle);
	}

	private void init(AttributeSet attrs, int defStyle) {
		// Load attributes
		final TypedArray a = getContext().obtainStyledAttributes(attrs,
				R.styleable.SlideControllerView, defStyle, 0);

		mExampleString = a
				.getString(R.styleable.SlideControllerView_exampleString);
		mExampleColor = a.getColor(
				R.styleable.SlideControllerView_exampleColor, mExampleColor);
		// Use getDimensionPixelSize or getDimensionPixelOffset when dealing
		// with
		// values that should fall on pixel boundaries.
		mExampleDimension = a.getDimension(
				R.styleable.SlideControllerView_exampleDimension,
				mExampleDimension);

		if (a.hasValue(R.styleable.SlideControllerView_exampleDrawable)) {
			mExampleDrawable = a
					.getDrawable(R.styleable.SlideControllerView_exampleDrawable);
			mExampleDrawable.setCallback(this);
		}
		if (a.hasValue(R.styleable.SlideControllerView_button)) {
			mDrawable_button = a
					.getDrawable(R.styleable.SlideControllerView_button);
			mDrawable_button.setCallback(this);
		}
		if (a.hasValue(R.styleable.SlideControllerView_background_button)) {
			mDrawable_background_button = a
					.getDrawable(R.styleable.SlideControllerView_background_button);
			mDrawable_background_button.setCallback(this);
		}

		a.recycle();

		// Set up a default TextPaint object
		mTextPaint = new TextPaint();
		mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
		mTextPaint.setTextAlign(Paint.Align.LEFT);
		// Set up a default TextPaint object
		mMaskPaint = new Paint();
		mMaskPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
		mMaskPaint.setAntiAlias(true);
		mMaskPaint.setDither(true);
		mMaskPaint.setColor(0xcccccc);

		mPath = new Path();
		paint_state = new Paint();

		mScroller = ScrollerCompat.create(getContext(), sInterpolator);
		// Update TextPaint and text measurements from attributes
		invalidateTextPaintAndMeasurements();
		
		setLayerType(View.LAYER_TYPE_SOFTWARE, null);
	}

	/**
	 * Interpolator defining the animation curve for mScroller
	 */
	private static final Interpolator sInterpolator = new Interpolator() {
		public float getInterpolation(float t) {
			t -= 1.0f;
			return t * t * t * t * t + 1.0f;
		}
	};
	int r = 0;
	RectF mleft = new RectF();
	RectF mcenter = new RectF();
	RectF mright = new RectF();
	RectF mContentRect = new RectF();
	private LinearGradient shader_stateFouse;
	private Paint paint_state;
	private int mDragState;

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		mPath.reset();
		mContentRect.set(0, 0, getWidth(), getHeight());
		mPath.addRoundRect(mContentRect, r/2,r/2, Path.Direction.CCW);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return handerTouchEvent(event);
	}

	private boolean handerTouchEvent(MotionEvent ev) {
		final int action = MotionEventCompat.getActionMasked(ev);
		System.out.println("handerTouchEvent");
		switch (action) {
		case MotionEvent.ACTION_DOWN: {

			cancel();
			final float x = ev.getX();
			final float y = ev.getY();
			saveInitialMotion(x, y);
			saveLastMotion(x, y);
			mDragState = STATE_DRAGGING;
			break;
		}

		case MotionEvent.ACTION_MOVE: {
			if (mDragState == STATE_DRAGGING) {
				float x = ev.getX();
				final float y = ev.getY();
				if (x > getWidth() - r) {
					x = getWidth() - r;
				}
				if (x - getPaddingLeft() <= 0) {
					x = getPaddingLeft();
				}
				saveLastMotion(x, y);
				invalidate();
			} else {
			}
			break;
		}
		case MotionEvent.ACTION_UP: {
			if (mDragState == STATE_DRAGGING) {
			}
			reslease();
			break;
		}

		case MotionEvent.ACTION_CANCEL: {
			cancel();
			break;
		}
		}
		return true;
	}

	private void reslease() {
		if (isClickable()) {
			if (x_last > getWidth() / 2) {
				mScroller.startScroll((int) x_last, (int) y_last,
						(int) getWidth() - r - (int) x_last, 0);
				invalidate();
			} else {
				mScroller.startScroll((int) x_last, (int) y_last,
						getPaddingLeft() - (int) x_last, 0);
				invalidate();
			}
		}

	}

	private void cancel() {
		if (isClickable()) {
			mScroller.abortAnimation();
		}

	}

	@Override
	public void computeScroll() {
		super.computeScroll();
		// 先判断mScroller滚动是否完成
		if (mScroller.computeScrollOffset()) {

			// 这里调用View的scrollTo()完成实际的滚动
			x_last = mScroller.getCurrX();
			y_last = mScroller.getCurrY();

			// 必须调用该方法,否则不一定能看到滚动效果
			invalidate();
		}
	}

	private float x_down;
	private float y_down;

	private void saveInitialMotion(float x, float y) {
		x_down = x;
		y_down = y;
	}

	private float x_last;
	private float y_last;

	private void saveLastMotion(float x, float y) {
		x_last = x;
		y_last = y;
	}

	private void invalidateTextPaintAndMeasurements() {
		mTextPaint.setTextSize(mExampleDimension);
		mTextPaint.setColor(mExampleColor);
		mTextWidth = mTextPaint.measureText(mExampleString);

		Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
		mTextHeight = fontMetrics.bottom;
	}
	@Override
	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		onDraw(canvas);
	}
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		// clip canvas,
		r = getHeight();
		// mPath.reset();
		// mPath.moveTo(r, 0);
		// mleft.set(0, 0, r, r);
		// mPath.arcTo(mleft, 90, 180);
		// mcenter.set(r / 2, 0, getWidth() - r / 2, r);
		// mPath.addRect(mcenter, Path.Direction.CW);
		// mPath.lineTo(getWidth() - r / 2, 0);
		// mright.set(getWidth() - r, 0, getWidth(), r);
		// mPath.arcTo(mright, 270, 180);

		// boolean clip = canvas.clipPath(mPath,
		// android.graphics.Region.Op.REPLACE);
		// System.out.println(clip);

		canvas.clipPath(mPath,android.graphics.Region.Op.REPLACE);


		System.out.println(r);
		// clip draw state,
		if (shader_stateFouse == null) {
			shader_stateFouse = new LinearGradient(0, 0, getWidth(),
					getHeight(), 0xffcccccc, COLOR_BLUE_LIGHT,
					Shader.TileMode.CLAMP);
			paint_state.setShader(shader_stateFouse);
		}
		paint_state.setAlpha((int) (0xff * (x_last / getWidth())));
		canvas.drawRect(0, 0, getWidth(), getHeight(), paint_state);

		// TODO: consider storing these as member variables to reduce
		// allocations per draw cycle.
		int paddingLeft = getPaddingLeft();
		int paddingTop = getPaddingTop();
		int paddingRight = getPaddingRight();
		int paddingBottom = getPaddingBottom();

		int contentWidth = getWidth() - paddingLeft - paddingRight;
		int contentHeight = getHeight() - paddingTop - paddingBottom;

		// Draw the text.
		canvas.drawText(mExampleString, paddingLeft
				+ (contentWidth - mTextWidth) / 2, paddingTop
				+ (contentHeight + mTextHeight) / 2, mTextPaint);

		// Draw the example drawable on top of the text.
		// if (mExampleDrawable != null) {
		// mExampleDrawable.setBounds(paddingLeft, paddingTop, paddingLeft
		// + contentWidth, paddingTop + contentHeight);
		// mExampleDrawable.draw(canvas);
		// }

		if (mDrawable_background_button != null) {
			mDrawable_background_button.getIntrinsicWidth();
			mDrawable_background_button.setBounds((int) x_last + paddingLeft,
					getHeight() / 2 - r / 2 + paddingTop, r + (int) x_last
							- paddingRight, getHeight() / 2 + getHeight() / 2
							- paddingBottom);
			// mDrawable_button.setBounds((int)x_last, 0,(int)x_last+r,
			// getHeight());
			mDrawable_background_button.draw(canvas);
		}

		if (mDrawable_button != null) {
			mDrawable_button.getIntrinsicWidth();
			mDrawable_button
					.setBounds(
							(int) x_last + r / 2
									- mDrawable_button.getIntrinsicWidth() / 2,
							getHeight() / 2
									- mDrawable_button.getIntrinsicHeight() / 2,
							(int) x_last + r / 2
									+ mDrawable_button.getIntrinsicWidth() / 2,
							getHeight() / 2
									+ mDrawable_button.getIntrinsicHeight() / 2);
			// mDrawable_button.setBounds((int)x_last, 0,(int)x_last+r,
			// getHeight());
			mDrawable_button.draw(canvas);
		}

	}

	/**
	 * Gets the example string attribute value.
	 * 
	 * @return The example string attribute value.
	 */
	public String getExampleString() {
		return mExampleString;
	}

	/**
	 * Sets the view's example string attribute value. In the example view, this
	 * string is the text to draw.
	 * 
	 * @param exampleString
	 *            The example string attribute value to use.
	 */
	public void setExampleString(String exampleString) {
		mExampleString = exampleString;
		invalidateTextPaintAndMeasurements();
	}

	/**
	 * Gets the example color attribute value.
	 * 
	 * @return The example color attribute value.
	 */
	public int getExampleColor() {
		return mExampleColor;
	}

	/**
	 * Sets the view's example color attribute value. In the example view, this
	 * color is the font color.
	 * 
	 * @param exampleColor
	 *            The example color attribute value to use.
	 */
	public void setExampleColor(int exampleColor) {
		mExampleColor = exampleColor;
		invalidateTextPaintAndMeasurements();
	}

	/**
	 * Gets the example dimension attribute value.
	 * 
	 * @return The example dimension attribute value.
	 */
	public float getExampleDimension() {
		return mExampleDimension;
	}

	/**
	 * Sets the view's example dimension attribute value. In the example view,
	 * this dimension is the font size.
	 * 
	 * @param exampleDimension
	 *            The example dimension attribute value to use.
	 */
	public void setExampleDimension(float exampleDimension) {
		mExampleDimension = exampleDimension;
		invalidateTextPaintAndMeasurements();
	}

	/**
	 * Gets the example drawable attribute value.
	 * 
	 * @return The example drawable attribute value.
	 */
	public Drawable getExampleDrawable() {
		return mExampleDrawable;
	}

	/**
	 * Sets the view's example drawable attribute value. In the example view,
	 * this drawable is drawn above the text.
	 * 
	 * @param exampleDrawable
	 *            The example drawable attribute value to use.
	 */
	public void setExampleDrawable(Drawable exampleDrawable) {
		mExampleDrawable = exampleDrawable;
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值