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 });