前段时间写了一篇项目总结的文章,总结了项目中使用的弧形View 和弧形ViewPager 效果,采用的是自定义View 的方法,而后绘制弧形采用的是二阶贝塞尔曲线,具体的思路和详情请看文章Android 项目总结(一):弧形ViewPager 和弧形HeaderView ,最后效果以下:java
虽然效果还不错,可是有瑕疵,有两个明显的缺陷:git
底部的圆弧不是正圆弧:如上图所示,弧形有点歪,特别是在小屏幕手机上表现尤其明显,由于是用二阶贝塞尔曲线绘制的圆弧,无论怎么调整控制点,都不会是一个正圆弧,以下图: github
圆弧不能设置图片背景:前面的这个版本,弧形背景只能设置颜色,不能设置背景图canvas
既然有了上面说的2个缺点,咱们就要想办法解决它,2个问题咱们逐个分析一下:api
1. 圆弧问题:网络
版本1的弧形使用二阶贝塞尔曲线绘制,既然这种方式不能绘制一个正圆弧,那么咱们不妨换个思路,哪些图形有正圆弧?首先就想到了圆,咱们能够绘制一个很大的圆,而后用手机的屏幕去截取,重叠的部分就是咱们想要View了,画了一个草图,看得比较直观:ide
如上图所示,圆形和屏幕的重叠区域就是咱们的View区域,圆形重叠以外的区域在屏幕外。这样截取出来的弧形确定是正圆弧。ui
2 . 弧形View设置图片背景this
咱们采用的是自定义View,显示图片仍是很简单的,canvas
的drawBitmap
就能实现,可是有一个点,图片要显示成咱们定义的弧形,这就须要用到 PorterDuffXfermode
,PorterDuff.Mode
,关于PorterDuffXfermode这里不过多的讲,网上讲它的博客不少,看一下这张经典的图就行白了: url
具体实现:先绘制圆,再绘制图片,设置 PorterDuffXfermode
为 PorterDuff.Mode.SRC_IN
就ok了。
前面说了思路,那么代码就很简单了,看一下实现的代码:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHeight = getHeight();
int width = getWidth();
mWidth = width;
// 半径
mRadius = width * 2;
// 矩形
mRect.left = 0;
mRect.top = 0;
mRect.right = width;
mRect.bottom = mHeight;
// 圆心坐标
mCircleCenter.x = width / 2;
mCircleCenter.y = mHeight - width * 2;
// 绘制渐变色
mLinearGradient = new LinearGradient(width / 2, 0, width / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
}
复制代码
解释:圆的半径为屏幕宽度2倍,矩形的高度就是整个自定义View的高度,圆心坐标的y 为 mHeight - mRadius 。
@Override
protected void onDraw(Canvas canvas) {
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
canvas.drawCircle(mCircleCenter.x, mCircleCenter.y, mRadius, mPaint);
//设置PorterDuffXfermode
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 经过变量mIsShowImage 来控制是显示图片仍是颜色
if (mIsShowImage) {
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, null, mRect, mPaint);
}
} else {
mPaint.setShader(mLinearGradient);//绘制渐变色
canvas.drawRect(mRect, mPaint);
}
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
复制代码
就是这么简单,最后效果以下:
效果是否是好了不少?
PerfectArcView.java
public class PerfectArcView extends View implements Target {
private Paint mPaint;
private Bitmap mBitmap;
private int mHeight;
private int mWidth;
private RectF mRect = new RectF();
private Point mCircleCenter;
private float mRadius;
private int mStartColor;
private int mEndColor;
private LinearGradient mLinearGradient;
/** * 显示图片仍是显示色值 */
private boolean mIsShowImage = true;
public PerfectArcView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
readAttr(attrs);
init();
}
private void init() {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
// mBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.splash);
mCircleCenter = new Point();
}
private void readAttr(AttributeSet set) {
TypedArray typedArray = getContext().obtainStyledAttributes(set, R.styleable.PerfectArcView);
mStartColor = typedArray.getColor(R.styleable.PerfectArcView_p_arc_startColor, Color.parseColor("#FF3A80"));
mEndColor = typedArray.getColor(R.styleable.PerfectArcView_p_arc_endColor, Color.parseColor("#FF3745"));
mIsShowImage = typedArray.getBoolean(R.styleable.PerfectArcView_p_arc_showImage, false);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHeight = getHeight();
int width = getWidth();
mWidth = width;
// 半径
mRadius = width * 2;
// 矩形
mRect.left = 0;
mRect.top = 0;
mRect.right = width;
mRect.bottom = mHeight;
// 圆心坐标
mCircleCenter.x = width / 2;
mCircleCenter.y = mHeight - width * 2;
mLinearGradient = new LinearGradient(width / 2, 0, width / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
}
/** * 加载网络图片 * * @param url */
public void setImageUrl(String url) {
Picasso.with(getContext()).load(url).into(this);
}
/** * @param startColor * @param endColor */
public void setColor(@ColorInt int startColor, @ColorInt int endColor) {
mStartColor = startColor;
mEndColor = endColor;
mIsShowImage = false;
mLinearGradient = new LinearGradient(mWidth / 2, 0, mWidth / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
invalidate();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onDraw(Canvas canvas) {
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
canvas.drawCircle(mCircleCenter.x, mCircleCenter.y, mRadius, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
if (mIsShowImage) {
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, null, mRect, mPaint);
}
} else {
mPaint.setShader(mLinearGradient);//绘制渐变色
canvas.drawRect(mRect, mPaint);
}
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Log.e("TAG", "onBitmapLoaded....");
mBitmap = bitmap;
invalidate();
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.e("TAG", "onBitmapFailed....");
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
Log.e("TAG", "onPrepareLoad....");
}
}
复制代码
条条大路通罗马,本文讲了弧形View的另外一种实现思路,固然了,可能还有不少种实现方法,上一篇文章的留言区里,有人同窗提到能够在矩形区域的地步覆盖一个白色的弧形图片,这个白色的能够找UI设计师切图,这种应该也是能够实现效果的,可是扩展性不是很强,若是项目中有多个地方用到,仍是挺麻烦的。若是你还有其余方法,欢迎交流。 源码访问Github:https://github.com/pinguo-zhouwei/AndroidTrainingSimples