这几天因为项目需求,需要在ImageView上面叠加一层透明圆弧,并且在沿着圆弧的方向显示相应的文字,效果如下图所示:
拿到这个需求,首先想到的是自定义一个ImageView来实现此功能,即在onDraw()中绘制圆弧和文字。同时因为要保证圆弧的位置可以任意摆放,圆弧的颜色、透明度以及文字大小、颜色等都是可控的,所以增加了一些自定义属性。实现代码非常简单,如下:
1.自定义ImageView:
1 package com.chunk.customviewsdemo.views.ArcImageView; 2 3 import android.content.Context; 4 import android.content.res.TypedArray; 5 import android.graphics.Canvas; 6 import android.graphics.Paint; 7 import android.graphics.Path; 8 import android.graphics.RectF; 9 import android.util.AttributeSet; 10 import android.widget.ImageView; 11 12 import com.chunk.customviewsdemo.R; 13 14 /** 15 * Description:A custom ImageView with circular arc and text 16 * Author: XiaoYu 17 * Date: 2016/5/10 13:55 18 */ 19 public class ArcImageView extends ImageView { 20 /** 21 * The default text size. 22 */ 23 private final float DEFAULT_TEXT_SIZE = 20; 24 /** 25 * The default scale value which decides the width of arc. 26 */ 27 private final float DEFAULT_SCALE = 0.5f; 28 /** 29 * The default transparency of arc. 30 */ 31 private final int DEFAULT_ARC_ALPHA =100; 32 /** 33 * The default width of arc. 34 */ 35 private final int DEFAULT_ARC_WIDTH =160; 36 /** 37 * The default angle that the arc starts with. 38 */ 39 private final int DEFAULT_START_ANGLE = 180; 40 /** 41 * The default angle that the arc. 42 */ 43 private final int DEFAULT_SWEEP_ANGLE = 90; 44 /** 45 * The default distance along the path to add to the text's starting position. 46 */ 47 private final int DEFAULT_H_OFFSET = 100; 48 /** 49 * The default distance above(-) or below(+) the path to position the text. 50 */ 51 private final int DEFAULT_V_OFFSET = 20; 52 private Context mContext; 53 /** 54 * The text displayed on ImageView along arc. 55 */ 56 private String mDrawStr; 57 /** 58 * The font size of text. 59 */ 60 private float mTextSize = DEFAULT_TEXT_SIZE; 61 /** 62 * The scale value which decides the width of arc. 63 */ 64 private float mScale = DEFAULT_SCALE; 65 /** 66 * The transparency of arc. 67 */ 68 private int mArcAlpha = DEFAULT_ARC_ALPHA; 69 /** 70 * The width of arc. 71 */ 72 private int mArcWidth = DEFAULT_ARC_WIDTH; 73 /** 74 * The start angle of angle. 75 */ 76 private int mStartAngle = DEFAULT_START_ANGLE; 77 /** 78 * The swept angle of angle. 79 */ 80 private int mSweepAngle = DEFAULT_SWEEP_ANGLE; 81 /** 82 * The default distance along the path to add to the text's starting position. 83 */ 84 private float mHOffset = DEFAULT_H_OFFSET; 85 /** 86 * The default distance above(-) or below(+) the path to position the text. 87 */ 88 private float mVOffset = DEFAULT_V_OFFSET; 89 /** 90 * The style of arc, all styles includes LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM, CENTER。 91 * of course, you can add your own style according to your demands. 92 */ 93 private int mDrawStyle; 94 /** 95 * The color of arc. 96 */ 97 private int mArcColor; 98 /** 99 * The color of text. 100 */ 101 private int mTextColor; 102 103 public ArcImageView(Context context) { 104 super(context); 105 this.mContext = context; 106 } 107 108 public ArcImageView(Context context, AttributeSet attrs) { 109 super(context, attrs); 110 this.mContext = context; 111 obtainAttributes(attrs); 112 } 113 114 public ArcImageView(Context context, AttributeSet attrs, int defStyleAttr) { 115 super(context, attrs, defStyleAttr); 116 this.mContext = context; 117 obtainAttributes(attrs); 118 } 119 120 /** 121 * Set the text that will be drawn on arc. 122 * @param drawStr the text content. 123 */ 124 public void setDrawStr(String drawStr) { 125 this.mDrawStr = drawStr; 126 //refresh this view 127 invalidate(); 128 } 129 130 /** 131 * Set the transparency of arc. 132 * @param mArcAlpha the value of transparency. 133 */ 134 public void setArcAlpha(int mArcAlpha) { 135 this.mArcAlpha = mArcAlpha; 136 //refresh this view 137 invalidate(); 138 } 139 140 @Override 141 protected void onDraw(Canvas canvas) { 142 super.onDraw(canvas); 143 //draw arc 144 Paint arcPaint = new Paint(); 145 arcPaint.setStrokeWidth(mArcWidth); 146 arcPaint.setStyle(Paint.Style.STROKE); 147 arcPaint.setColor(mArcColor); 148 arcPaint.setAlpha(mArcAlpha); 149 int width = getWidth(); 150 int height = getHeight(); 151 float radius; 152 if (width > height) { 153 radius = mScale * height; 154 } else { 155 radius = mScale * width; 156 } 157 RectF oval = new RectF(); 158 159 int center_x = width; 160 int center_y = height; 161 162 switch (mDrawStyle) { 163 case 0: 164 center_x = 0; 165 center_y = 0; 166 mStartAngle = 90; 167 mSweepAngle = -90; 168 break; 169 case 1: 170 center_x = 0; 171 center_y = height; 172 mStartAngle = 270; 173 mSweepAngle = 90; 174 break; 175 case 2: 176 center_x = width; 177 center_y = 0; 178 mStartAngle = 180; 179 mSweepAngle = -90; 180 break; 181 case 3: 182 center_x = width; 183 center_y = height; 184 mStartAngle = 180; 185 mSweepAngle = 90; 186 break; 187 case 4: 188 center_x = width / 2; 189 center_y = height / 2; 190 mStartAngle = 270; 191 mSweepAngle = 90; 192 break; 193 } 194 float left = center_x - radius; 195 float top = center_y - radius; 196 float right = center_x + radius; 197 float bottom = center_y + radius; 198 oval.set(left, top, right, bottom); 199 canvas.drawArc(oval, mStartAngle, mSweepAngle, false, arcPaint); 200 201 //draw text 202 Paint textPaint = new Paint(); 203 textPaint.setTextSize(mTextSize); 204 textPaint.setStyle(Paint.Style.FILL); 205 textPaint.setColor(mTextColor); 206 Path path = new Path(); 207 path.addArc(oval, mStartAngle, mSweepAngle); 208 canvas.drawTextOnPath(mDrawStr, path, mHOffset, mVOffset, textPaint); 209 } 210 211 /** 212 * Obtain custom attributes that been defined in attrs.xml. 213 * @param attrs A collection of attributes. 214 */ 215 private void obtainAttributes(AttributeSet attrs) { 216 TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.ArcImageView); 217 mDrawStr = ta.getString(R.styleable.ArcImageView_drawStr); 218 mTextSize = ta.getDimension(R.styleable.ArcImageView_textSize, DEFAULT_TEXT_SIZE); 219 mArcAlpha = ta.getInteger(R.styleable.ArcImageView_arcAlpha, DEFAULT_ARC_ALPHA); 220 mArcWidth = ta.getInteger(R.styleable.ArcImageView_arcWidth, DEFAULT_ARC_WIDTH); 221 mStartAngle = ta.getInteger(R.styleable.ArcImageView_startAngle, DEFAULT_START_ANGLE); 222 mSweepAngle = ta.getInteger(R.styleable.ArcImageView_startAngle, DEFAULT_SWEEP_ANGLE); 223 mHOffset = ta.getInteger(R.styleable.ArcImageView_hOffset, DEFAULT_H_OFFSET); 224 mVOffset = ta.getInteger(R.styleable.ArcImageView_vOffset, DEFAULT_V_OFFSET); 225 mArcColor = ta.getColor(R.styleable.ArcImageView_arcColor, 0XCCCCCC); 226 mTextColor = ta.getColor(R.styleable.ArcImageView_textColor, 0XFFFFFF); 227 mDrawStyle = ta.getInt(R.styleable.ArcImageView_drawStyle, 0); 228 ta.recycle(); 229 } 230 }
2.在values文件夹下的attrs.xml中自定义属性:
1 <?xml version="1.0" encoding="utf-8"?> 2 <resources> 3 <declare-styleable name="ArcImageView"> 4 <attr name="drawStr" format="string" /> 5 <attr name="textSize" format="dimension" /> 6 <attr name="arcAlpha" format="integer" /> 7 <attr name="arcWidth" format="integer" /> 8 <attr name="startAngle" format="integer" /> 9 <attr name="sweepAngle" format="integer" /> 10 <attr name="scale" format="float" /> 11 <attr name="hOffset" format="float" /> 12 <attr name="vOffset" format="float" /> 13 <attr name="drawStyle" format="enum"> 14 <enum name="LEFT_TOP" value="0" /> 15 <enum name="LEFT_BOTTOM" value="1" /> 16 <enum name="RIGHT_TOP" value="2" /> 17 <enum name="RIGHT_BOTTOM" value="3" /> 18 <enum name="CENTER" value="4" /> 19 </attr> 20 <attr name="arcColor" format="color" /> 21 <attr name="textColor" format="color" /> 22 </declare-styleable> 23 </resources>
3.在MainActivity调用ArcImageView,实现代码如下:
1 package com.chunk.customviewsdemo; 2 3 import android.os.Bundle; 4 import android.support.v7.app.AppCompatActivity; 5 import android.view.View; 6 import android.widget.Button; 7 8 import com.chunk.customviewsdemo.views.ArcImageView.ArcImageView; 9 10 public class MainActivity extends AppCompatActivity implements View.OnClickListener { 11 private ArcImageView aiv_one; 12 private ArcImageView aiv_two; 13 private ArcImageView aiv_three; 14 private ArcImageView aiv_four; 15 private Button btn_another_one; 16 private int mGroup = 1; 17 18 @Override 19 protected void onCreate(Bundle savedInstanceState) { 20 super.onCreate(savedInstanceState); 21 setContentView(R.layout.activity_main); 22 aiv_one = (ArcImageView) findViewById(R.id.aiv_one); 23 aiv_one.setArcAlpha(180); 24 aiv_two = (ArcImageView) findViewById(R.id.aiv_two); 25 aiv_two.setArcAlpha(180); 26 aiv_three = (ArcImageView) findViewById(R.id.aiv_three); 27 aiv_three.setArcAlpha(180); 28 aiv_four = (ArcImageView) findViewById(R.id.aiv_four); 29 aiv_four.setArcAlpha(180); 30 btn_another_one = (Button) findViewById(R.id.btn_another_one); 31 btn_another_one.setOnClickListener(this); 32 } 33 34 @Override 35 public void onClick(View v) { 36 switch (v.getId()) { 37 case R.id.btn_another_one: 38 if (mGroup == 1) { 39 aiv_one.setDrawStr("苹果"); 40 aiv_one.setBackgroundResource(R.drawable.apple); 41 aiv_two.setDrawStr("柚子"); 42 aiv_two.setBackgroundResource(R.drawable.pineapple); 43 aiv_three.setDrawStr("香蕉"); 44 aiv_three.setBackgroundResource(R.drawable.banana); 45 aiv_four.setDrawStr("菠萝"); 46 aiv_four.setBackgroundResource(R.drawable.pineapple); 47 mGroup = 2; 48 } else { 49 aiv_one.setDrawStr("牛排"); 50 aiv_one.setBackgroundResource(R.drawable.steak); 51 aiv_two.setDrawStr("海鲜"); 52 aiv_two.setBackgroundResource(R.drawable.seafood); 53 aiv_three.setDrawStr("奶酪"); 54 aiv_three.setBackgroundResource(R.drawable.cheese); 55 aiv_four.setDrawStr("烧烤"); 56 aiv_four.setBackgroundResource(R.drawable.barbecue); 57 mGroup = 1; 58 } 59 break; 60 } 61 } 62 }
4.MainActivity的布局文件如下:
1 <LinearLayout 2 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:custom="http://schemas.android.com/apk/res-auto" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:layout_marginTop="100dp" 7 android:layout_marginBottom="100dp" 8 android:orientation="vertical" > 9 10 <Button 11 android:id="@+id/btn_another_one" 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:text="换一组" /> 15 16 <LinearLayout 17 android:layout_width="match_parent" 18 android:layout_height="0dp" 19 android:layout_weight="1" 20 android:orientation="horizontal" > 21 22 <RelativeLayout 23 android:layout_width="0dp" 24 android:layout_weight="1" 25 android:layout_height="match_parent" > 26 27 <com.chunk.customviewsdemo.views.ArcImageView.ArcImageView 28 android:id="@+id/aiv_one" 29 android:layout_width="match_parent" 30 android:layout_height="match_parent" 31 android:background="@drawable/steak" 32 custom:drawStyle="RIGHT_BOTTOM" 33 custom:drawStr="牛排" 34 custom:arcAlpha="100" 35 custom:arcColor="@color/gray" 36 custom:textColor="@color/black" 37 custom:textSize="20sp" /> 38 </RelativeLayout> 39 40 <RelativeLayout 41 android:layout_width="0dp" 42 android:layout_weight="1" 43 android:layout_height="match_parent" > 44 45 <com.chunk.customviewsdemo.views.ArcImageView.ArcImageView 46 android:id="@+id/aiv_two" 47 android:layout_width="match_parent" 48 android:layout_height="match_parent" 49 android:background="@drawable/seafood" 50 custom:drawStyle="LEFT_BOTTOM" 51 custom:drawStr="海鲜" 52 custom:arcAlpha="100" 53 custom:arcColor="@color/gray" 54 custom:textColor="@color/black" 55 custom:textSize="20sp" /> 56 57 </RelativeLayout> 58 </LinearLayout> 59 60 <LinearLayout 61 android:layout_width="match_parent" 62 android:layout_height="0dp" 63 android:layout_weight="1" 64 android:orientation="horizontal" > 65 66 <RelativeLayout 67 android:layout_width="0dp" 68 android:layout_weight="1" 69 android:layout_height="match_parent" > 70 71 <com.chunk.customviewsdemo.views.ArcImageView.ArcImageView 72 android:id="@+id/aiv_three" 73 android:layout_width="match_parent" 74 android:layout_height="match_parent" 75 android:background="@drawable/cheese" 76 custom:drawStyle="RIGHT_TOP" 77 custom:drawStr="奶酪" 78 custom:arcAlpha="100" 79 custom:arcColor="@color/gray" 80 custom:textColor="@color/black" 81 custom:textSize="20sp" /> 82 </RelativeLayout> 83 84 <RelativeLayout 85 android:layout_width="0dp" 86 android:layout_weight="1" 87 android:layout_height="match_parent" > 88 89 <com.chunk.customviewsdemo.views.ArcImageView.ArcImageView 90 android:id="@+id/aiv_four" 91 android:layout_width="match_parent" 92 android:layout_height="match_parent" 93 android:background="@drawable/barbecue" 94 custom:drawStyle="LEFT_TOP" 95 custom:drawStr="烧烤" 96 custom:arcAlpha="100" 97 custom:arcColor="@color/gray" 98 custom:textColor="@color/black" 99 custom:textSize="20sp" /> 100 101 </RelativeLayout> 102 </LinearLayout> 103 </LinearLayout>
注意,在布局文件中引入自定义属性时需要加入一行代码:xmlns:custom="http://schemas.android.com/apk/res-auto"。
好了,需求搞定,剩下的就是搬到实际的项目当中去了。实现效果如下:
总结一下,自定义View一般就是通过重写onDraw、onMeasure()、onLayout()等方法来进行测量、绘制,绘制的时候一般会用到Canvas、Paint、Bitmap等类,测量和绘制的过程其实就是对现实生活中绘图工作的抽象和实现,我们利用面向对象的思想将画板、画纸、画笔等工具以及绘画的动作用一行行代码加以描述就OK啦!
由于实现过程比较简单,我就不贴源码了,大家如果对2D绘图还不是很了解,可以去搜一下相关资料或查阅相关书籍!