android自定义view,实现竖直方向的文字功能,文字方向朝上,同时提供接口,判断当前touch的是哪个字符,并改变颜色。
由于时间比较仓促,因此没有对代码进行过多的优化,功能远远不如android的自带的TextView强大,只是继承于view,而不是textview。
主要用途:电话本的侧边快速导航等
效果图:(自定义字符串 “#ABCDEFGHIJKLMN),可以实现自定义任意字符串
view的实现:
1 package cn.carbs.verticalstraighttextview.view; 2 3 import cn.carbs.verticalstraighttextview.R; 4 import android.content.Context; 5 import android.content.res.TypedArray; 6 import android.graphics.Canvas; 7 import android.graphics.Paint.Align; 8 import android.graphics.Rect; 9 import android.text.TextPaint; 10 import android.util.AttributeSet; 11 import android.util.Log; 12 import android.util.TypedValue; 13 import android.view.View; 14 /** 15 * 参考资料: 16 * http://chris.banes.me/2014/03/27/measuring-text/ 17 * http://blog.163.com/gobby_1110/blog/static/2928171520136304172378/ 18 * @author Rick.Wang 19 * 20 */ 21 public class VerticalStraightTextView extends View { 22 23 private final static int DEFAULT_TEXT_SIZE = 15; 24 private final static int DEFAULT_TEXT_COLOR = 0xFF000000; 25 private final static int DEFAULT_TEXT_COLOR_PICKED = 0xFF990000; 26 private final static int DEFAULT_CHAR_SPACE = 0; 27 28 private TextPaint mTextPaint; 29 private TextPaint mTextPaintPicked; 30 private String mText = ""; 31 32 private int mTextLength = 0; 33 private int mCharGap = 0; 34 private int mCharWidth = 0; 35 private int mCharHeight = 0; 36 37 private int currPickedCharIndex = -1; 38 39 float[] coordinates = null; 40 41 public float[] getCoordinates(){ 42 return coordinates; 43 } 44 45 public VerticalStraightTextView(Context context) { 46 super(context); 47 init(); 48 } 49 50 public VerticalStraightTextView(Context context, AttributeSet attrs) { 51 super(context, attrs); 52 init(); 53 54 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.verticalstraighttextview); 55 56 int n = a.getIndexCount(); 57 for (int i = 0; i < n; i++) { 58 int attr = a.getIndex(i); 59 switch (attr) { 60 case R.styleable.verticalstraighttextview_text: 61 mText = a.getString(attr); 62 if(mText == null){ 63 mText = ""; 64 break; 65 } 66 mTextLength = mText.length(); 67 break; 68 case R.styleable.verticalstraighttextview_textSize: 69 int textSize = a.getDimensionPixelOffset(R.styleable.verticalstraighttextview_textSize, DEFAULT_TEXT_SIZE); 70 if (textSize > 0) { 71 mTextPaint.setTextSize(textSize); 72 mTextPaintPicked.setTextSize(textSize); 73 } 74 break; 75 76 case R.styleable.verticalstraighttextview_charGap: 77 mCharGap = a.getDimensionPixelSize(R.styleable.verticalstraighttextview_charGap, (int) TypedValue.applyDimension( 78 TypedValue.COMPLEX_UNIT_PX, DEFAULT_CHAR_SPACE, getResources().getDisplayMetrics())); 79 break; 80 case R.styleable.verticalstraighttextview_textColor: 81 mTextPaint.setColor(a.getColor(R.styleable.verticalstraighttextview_textColor, DEFAULT_TEXT_COLOR)); 82 break; 83 case R.styleable.verticalstraighttextview_textColorPicked: 84 mTextPaintPicked.setColor(a.getColor(R.styleable.verticalstraighttextview_textColorPicked, DEFAULT_TEXT_COLOR_PICKED)); 85 break; 86 } 87 } 88 a.recycle(); 89 90 requestLayout(); 91 invalidate(); 92 } 93 94 private final void init() { 95 mTextPaint = new TextPaint(); 96 mTextPaint.setAntiAlias(true); 97 mTextPaint.setTextSize(DEFAULT_TEXT_SIZE); 98 mTextPaint.setTextAlign(Align.CENTER); 99 mTextPaintPicked = new TextPaint(mTextPaint); 100 mTextPaint.setColor(DEFAULT_TEXT_COLOR); 101 mTextPaintPicked.setColor(DEFAULT_TEXT_COLOR_PICKED); 102 } 103 104 public void setText(String text) { 105 if(text == null){ 106 text = ""; 107 } 108 if(!mText.equals(text)){ 109 mText = text; 110 mTextLength = text.length(); 111 requestLayout(); 112 invalidate(); 113 } 114 } 115 116 public void setTextSize(int size) { 117 if(mTextPaint.getTextSize() != size){ 118 mTextPaint.setTextSize(size); 119 mTextPaintPicked.setTextSize(size); 120 requestLayout(); 121 invalidate(); 122 } 123 } 124 125 public void setTextColor(int color) { 126 if(color != mTextPaint.getColor()){ 127 mTextPaint.setColor(color); 128 invalidate(); 129 } 130 } 131 132 public void setTextColorPicked(int color) { 133 if(color != mTextPaintPicked.getColor()){ 134 mTextPaintPicked.setColor(color); 135 invalidate(); 136 } 137 } 138 139 public int getCharHeight(){ 140 return mCharGap + mCharHeight; 141 } 142 143 @Override 144 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 145 Log.d("1218", "onMeasure"); 146 //获取字体宽度 147 float maxCharWidth = 0f; 148 for(int i = 0; i < mTextLength; i++){ 149 maxCharWidth = Math.max(mTextPaint.measureText(mText.substring(i, i+1)), maxCharWidth); 150 } 151 mCharWidth = (int)Math.ceil(maxCharWidth); 152 153 //获取字体高度 154 Rect textBounds = new Rect(); 155 mTextPaint.getTextBounds(mText, 0, mTextLength, textBounds); 156 mCharHeight = textBounds.height(); 157 158 setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); 159 } 160 161 private int measureWidth(int measureSpec) { 162 int result = 0; 163 int specMode = MeasureSpec.getMode(measureSpec); 164 int specSize = MeasureSpec.getSize(measureSpec); 165 166 if (specMode == MeasureSpec.EXACTLY) { 167 result = specSize; 168 } else { 169 result = this.getPaddingLeft() + this.getPaddingRight() + mCharWidth; 170 if (specMode == MeasureSpec.AT_MOST) { 171 result = Math.min(result, specSize); 172 } 173 } 174 return result; 175 } 176 177 private int measureHeight(int measureSpec) { 178 int result = 0; 179 int specMode = MeasureSpec.getMode(measureSpec); 180 int specSize = MeasureSpec.getSize(measureSpec); 181 182 if (specMode == MeasureSpec.EXACTLY) { 183 result = specSize; 184 } else { 185 result = getPaddingTop() + getPaddingBottom(); 186 if(mTextLength > 0){ 187 result += mTextLength * (mCharGap + mCharHeight) - mCharGap; 188 } 189 if (specMode == MeasureSpec.AT_MOST) { 190 result = Math.min(result, specSize); 191 } 192 } 193 return result; 194 } 195 196 @Override 197 protected void onDraw(Canvas canvas) { 198 super.onDraw(canvas); 199 Log.d("1218", "onDraw"); 200 if(mTextLength == 0){ 201 return; 202 } 203 204 int height = getMeasuredHeight(); 205 int measuredWidth = getMeasuredWidth(); 206 207 int paddingTop = getPaddingTop(); 208 int paddingBottom = getPaddingBottom(); 209 int paddingLeft = getPaddingLeft(); 210 int paddingRight = getPaddingRight(); 211 212 //默认居中 213 int x = paddingLeft + (measuredWidth - paddingLeft - paddingRight)/2; 214 int y = 0; 215 216 int cellHeight = (height - paddingTop - paddingBottom)/ mTextLength; 217 //TODO 可能会有bug 218 if(coordinates == null || coordinates.length != mTextLength){ 219 coordinates = new float[mTextLength + 1]; 220 } 221 coordinates[0] = 0; 222 for(int i = 0; i < mTextLength; i++){ 223 y = paddingTop + i * cellHeight + cellHeight/2; 224 coordinates[i + 1] = y + cellHeight/2; 225 if(currPickedCharIndex != i){ 226 canvas.drawText(mText, i, i + 1, x, y, mTextPaint); 227 }else{ 228 canvas.drawText(mText, i, i + 1, x, y, mTextPaintPicked); 229 } 230 } 231 coordinates[mTextLength] = height; 232 } 233 234 //y is the coordinate-Y 235 //this function can return the "touched char" 236 public int getPickedCharIndex(float[] coordinates, float y){ 237 int start = 0; 238 int end = coordinates.length - 1; 239 while (start != end - 1) { 240 int middle = (start + end) / 2; 241 if (y < coordinates[middle]) { 242 end = middle; 243 } else if (y > coordinates[middle]) { 244 start = middle; 245 } 246 } 247 return start; 248 } 249 250 251 /*************************************** 252 * 253 * +---------------+ <-- Y == coordinates[0] 254 * | # | 255 * |---------------| coordinates[1] 256 * | A | 257 * |---------------| coordinates[2] 258 * | B | 259 * |---------------| coordinates[3] 260 * | C | 261 * +---------------| coordinates[4] 262 ***************************************/ 263 264 public int getPickedCharIndex(float y){ 265 //优化查询 266 //如果当前的>-1,说明正在touchEvent 267 if(currPickedCharIndex > -1){ 268 if(coordinates[currPickedCharIndex] < y && y < coordinates[currPickedCharIndex+1]){ 269 return currPickedCharIndex; 270 } 271 } 272 273 int start = 0; 274 int end = coordinates.length - 1; 275 while (start != end - 1) { 276 int middle = (start + end) / 2; 277 if (y < coordinates[middle]) { 278 end = middle; 279 } else if (y > coordinates[middle]) { 280 start = middle; 281 } 282 } 283 return start; 284 } 285 286 287 public void setCurrPickedCharIndex(int index){ 288 if(currPickedCharIndex != index){ 289 currPickedCharIndex = index; 290 invalidate(); 291 } 292 } 293 294 }
style文件的定义:(将此代码写入values文件夹下的styles.xml文件中)
1 <declare-styleable name="verticalstraighttextview"> 2 <attr name= "text" format ="string" /> 3 <attr name= "textColor" format ="reference|color" /> 4 <attr name= "textColorPicked" format="reference|color" /> 5 <attr name= "textSize" format="reference|dimension" /> 6 <attr name= "charGap" format ="reference|dimension" /> 7 declare-styleable >
布局文件引入此自定义view:
1 <cn.carbs.verticalstraighttextview.view.VerticalStraightTextView 2 android:id="@+id/kk" 3 android:layout_width="wrap_content" 4 android:padding="5dp" 5 android:layout_height="fill_parent" 6 android:background="#33333333" 7 app:textSize="20sp" 8 app:text= "#ABCEDFGHIJKLMN" />
在activity中的使用:
1 verticalView = (VerticalStraightTextView)this.findViewById(R.id.kk); 2 3 verticalView.setOnClickListener(new View.OnClickListener() { 4 @Override 5 public void onClick(View v) { 6 Toast.makeText(getApplicationContext(), "onclick", Toast.LENGTH_SHORT).show(); 7 } 8 }); 9 10 verticalView.setOnTouchListener(new View.OnTouchListener() { 11 12 @Override 13 public boolean onTouch(View view, MotionEvent event) { 14 switch(event.getAction()){ 15 case MotionEvent.ACTION_DOWN: 16 verticalView.setCurrPickedCharIndex(verticalView.getPickedCharIndex(event.getY())); 17 break; 18 case MotionEvent.ACTION_MOVE: 19 verticalView.setCurrPickedCharIndex(verticalView.getPickedCharIndex(event.getY())); 20 break; 21 case MotionEvent.ACTION_UP: 22 verticalView.setCurrPickedCharIndex(-1); 23 break; 24 case MotionEvent.ACTION_CANCEL: 25 verticalView.setCurrPickedCharIndex(-1); 26 break; 27 } 28 return true; 29 } 30 });