前段時間寫了一篇項目總結的文章,總結了項目中使用的弧形View 和弧形ViewPager 效果互亮,采用的是自定義View 的方法,然后繪制弧形采用的是二階貝塞爾曲線余素,具體的思路和詳情請看文章Android 項目總結(一):弧形ViewPager 和弧形HeaderView ,最后效果如下:
雖然效果還不錯豹休,但是有瑕疵,有兩個明顯的缺陷:
- 底部的圓弧不是正圓弧:如上圖所示桨吊,弧形有點歪威根,特別是在小屏幕手機上表現(xiàn)尤為明顯凤巨,因為是用二階貝塞爾曲線繪制的圓弧,不管怎么調(diào)整控制點洛搀,都不會是一個正圓弧敢茁,如下圖:
- 圓弧不能設置圖片背景:前面的這個版本,弧形背景只能設置顏色留美,不能設置背景圖
1. 升級版ArcView實現(xiàn)思路
既然有了上面說的2個缺點彰檬,我們就要想辦法解決它,2個問題我們逐個分析一下:
1. 圓弧問題:
版本1的弧形使用二階貝塞爾曲線繪制谎砾,既然這種方式不能繪制一個正圓弧僧叉,那么我們不妨換個思路,哪些圖形有正圓还桌啤?首先就想到了圓隘道,我們可以繪制一個很大的圓症歇,然后用手機的屏幕去截取,重疊的部分就是我們想要View了谭梗,畫了一個草圖忘晤,看得比較直觀:
如上圖所示,圓形和屏幕的重疊區(qū)域就是我們的View區(qū)域激捏,圓形重疊之外的區(qū)域在屏幕外设塔。這樣截取出來的弧形肯定是正圓弧。
2 . 弧形View設置圖片背景
我們采用的是自定義View,顯示圖片還是很簡單的,canvas
的drawBitmap
就能實現(xiàn)远舅,但是有一個點闰蛔,圖片要顯示成我們定義的弧形,這就需要用到 PorterDuffXfermode
,PorterDuff.Mode
,關于PorterDuffXfermode這里不過多的講图柏,網(wǎng)上講它的博客很多序六,看一下這張經(jīng)典的圖就行白了:
具體實現(xiàn):先繪制圓,再繪制圖片蚤吹,設置 PorterDuffXfermode
為 PorterDuff.Mode.SRC_IN
就ok了例诀。
2. 具體實現(xiàn)
前面說了思路,那么代碼就很簡單了裁着,看一下實現(xiàn)的代碼:
@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);
}
就是這么簡單扔罪,最后效果如下:
效果是不是好了很多?
3. 完整源碼
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);
}
/**
* 加載網(wǎng)絡圖片
*
* @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....");
}
}
4. 最后
條條大路通羅馬诸蚕,本文講了弧形View的另一種實現(xiàn)思路步势,當然了氧猬,可能還有很多種實現(xiàn)方法,上一篇文章的留言區(qū)里坏瘩,有人同學提到可以在矩形區(qū)域的地步覆蓋一個白色的弧形圖片柜某,這個白色的可以找UI設計師切圖,這種應該也是可以實現(xiàn)效果的嚼酝,但是擴展性不是很強毙驯,如果項目中有多個地方用到,還是挺麻煩的哪自。如果你還有其他方法丰包,歡迎交流。
源碼訪問Github:https://github.com/pinguo-zhouwei/AndroidTrainingSimples