封面. png
前段时间写了一篇项目总结的文章,总结了项目中使用的弧形 View 和弧形 ViewPager 效果,采用的是自定义 View 的方法,然后绘制弧形采用的是二阶贝塞尔曲线,具体的思路和详情请看文章 Android 项目总结 (一):弧形 ViewPager 和弧形 HeaderView , 最后效果如下:
image.png
弧形 viewpager.gif
虽然效果还不错,但是有瑕疵,有两个明显的缺陷:
image.png
既然有了上面说的 2 个缺点,我们就要想办法解决它,2 个问题我们逐个分析一下:
1. 圆弧问题:版本 1 的弧形使用二阶贝塞尔曲线绘制,既然这种方式不能绘制一个正圆弧,那么我们不妨换个思路,哪些图形有正圆弧?首先就想到了圆,我们可以绘制一个很大的圆,然后用手机的屏幕去截取,重叠的部分就是我们想要 View 了,画了一个草图,看得比较直观:
image.png
如上图所示,圆形和屏幕的重叠区域就是我们的 View 区域,圆形重叠之外的区域在屏幕外。这样截取出来的弧形肯定是正圆弧。
2 . 弧形 View 设置图片背景我们采用的是自定义 View, 显示图片还是很简单的, canvas 的 drawBitmap 就能实现,但是有一个点,图片要显示成我们定义的弧形,这就需要用到 PorterDuffXfermode,PorterDuff.Mode, 关于 PorterDuffXfermode 这里不过多的讲,网上讲它的博客很多,看一下这张经典的图就行白了:
image.png
具体实现:先绘制圆,再绘制图片,设置 PorterDuffXfermode 为
就 ok 了。
- PorterDuff.Mode.SRC_IN
前面说了思路,那么代码就很简单了,看一下实现的代码:
- @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);
- }
就是这么简单,最后效果如下:
image.png
效果是不是好了很多?
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
来源: http://www.jianshu.com/p/db4b7290d98c