我们知道,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;
}
}