標籤:android 控制項 自訂 textview 體驗
在前面的一些關於Android的博文中,涉及到一些自訂控制項的文章。那麼,在這篇博文中,我將向大家介紹以自訂控制項的方式來實現羅盤視圖的效果。我們將會通過擴充View類來建立一個新的羅盤視圖。它通過顯示傳統的羅盤來指示當前朝向的方向。
一、實現1、建立CompassView類
這個類擴充自View類,然後添加允許在代碼中對視圖進行執行個體化或者從資源布局填充它的建構函式。之後,添加一個新的initCompassView方法,用來初始化控制項,並在每個建構函式中調用它。
具體結構代碼如下:
package com.lyz.compass.view;import android.content.Context;import android.util.AttributeSet;import android.view.View;/** * 自訂View類 * @author liuyazhuang * */public class CompassView extends View {public CompassView(Context context){super(context);initCompassView();}public CompassView(Context context, AttributeSet attrs){super(context, attrs);initCompassView();}public CompassView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initCompassView();}/** * 初始化視圖的各個屬性 */protected void initCompassView(){setFocusable(true);}}
2、覆寫onMeasure方法
羅盤視圖應該是一個正圓,而且應該佔據畫布允許的儘可能大的空間。因此,可以通過重寫onMeasure方法來計算最短邊的長度,然後通過這個值並通過setMeasuredDimension方法來設定高度和寬度。
具體代碼如下:
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//羅盤是一個儘可能填充更多的空間的圓,通過設定最短的邊界,高度或者寬度來設定測量的尺寸int measureWidth = measure(widthMeasureSpec);int measureHeight = measure(heightMeasureSpec);int d = Math.min(measureWidth, measureHeight);setMeasuredDimension(d, d);}
/** * 解碼資料值 * @param measureSpec * @return */private int measure(int measureSpec) {int result = 0;//對測量說明進行解碼int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);//如果沒有指定界限,則返回預設大小200if(specMode == MeasureSpec.UNSPECIFIED){result = 200;}else{//由於是希望填充可用的空間,所以總是返回整個可用的邊界result = specSize;}return result;}
3、修改activity_main.xml
用新的CompassView替換TextView
具體代碼如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 引用自訂的View --> <com.lyz.compass.view.CompassView android:id="@+id/compassView" android:layout_width="match_parent" android:layout_height="match_parent"/></FrameLayout>
4、在資源檔中定義羅盤屬性1)在res/values/strings.xml中建立文本資源
<?xml version="1.0" encoding="utf-8"?><resources> <string name="app_name">Compass</string> <string name="action_settings">Settings</string> <string name="hello_world">Hello world!</string> <string name="cardinal_north">N</string> <string name="cardinal_east">E</string> <string name="cardinal_south">S</string> <string name="cardinal_west">W</string> </resources>
2)在res/values/colors.xml檔案中建立顏色資源
<?xml version="1.0" encoding="utf-8"?><resources> <color name="background_color">#555</color> <color name="marker_color">#AFFF</color> <color name="text_color">#AFFF</color></resources>
5、在CompassView類添加新屬性
在CompassView類中,為顯示的方向添加一個新的屬性,並建立它的set和get方法。
//顯示的方向private float bearing;public float getBearing() {return bearing;}public void setBearing(float bearing) {this.bearing = bearing;}
6、引用資源檔
返回到CompassView類,引用4中建立的每一個資源,把字串值儲存為執行個體變數,並使用顏色值來建立新的類範圍的Paint對象。在下一步中將使用這些對象來繪製羅盤字盤。
具體代碼如下:
//顯示的方向private float bearing;private Paint markerPaint;private Paint textPaint;private Paint circlePaint;private String northString;private String eastString;private String southString;private String westString;private int textHeight;/** * 初始化視圖的各個屬性 */protected void initCompassView(){setFocusable(true);Resources r = this.getResources();circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);circlePaint.setColor(r.getColor(R.color.background_color));circlePaint.setStrokeWidth(1);circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);northString = r.getString(R.string.cardinal_north);eastString = r.getString(R.string.cardinal_east);southString = r.getString(R.string.cardinal_south);westString = r.getString(R.string.cardinal_west);textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);textPaint.setColor(r.getColor(R.color.text_color));textHeight = (int) textPaint.measureText("yY");markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);markerPaint.setColor(r.getColor(R.color.marker_color));}
7、繪製羅盤
使用6中建立的String和Paint來繪製羅盤的字盤。
具體代碼如下:
@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stub//super.onDraw(canvas);int measuredWidth = getMeasuredWidth();int measuredHeight = getMeasuredHeight();int px = measuredWidth / 2;int py = measuredHeight / 2;//取較小的值為半徑int radius = Math.min(px, py);//繪製背景canvas.drawCircle(px, py, radius, circlePaint);canvas.save();canvas.rotate(-bearing, px, py);int textWidth = (int) textPaint.measureText("W");int cadinalX = px - textWidth / 2;int cadinalY = py - radius + textHeight;//每15度繪製一個標記,每45度繪製一個文本for(int i = 0; i < 24; i++){//繪製一個標記canvas.drawLine(px, px - radius, py, py - radius + 10, markerPaint);canvas.save();canvas.translate(0, textHeight);//繪製基本方位if(i % 6 == 0){String dirString = "";switch (i) {case 0:dirString = northString;int arrowY = 2 * textHeight;canvas.drawLine(px , arrowY, px - 5, 3 * textHeight, markerPaint);canvas.drawLine(px, arrowY, px + 5, 3 * textHeight, markerPaint);break;case 6:dirString = eastString;break;case 12:dirString = southString;break;case 18:dirString = westString;break;default:break;}canvas.drawText(dirString, cadinalX, cadinalY, textPaint);}else if(i % 3 == 0){//每45度繪製文本String angle = String.valueOf(i * 15);float angleTextWidth = textPaint.measureText(angle);int angleTextX = (int)(px - angleTextWidth/2);int angleTextY = py - radius + textHeight;canvas.drawText(angle, angleTextX, angleTextY, textPaint);}canvas.restore();canvas.rotate(15, px, py);}canvas.restore();}
8、添加可訪問性支援
羅盤視圖以可視方式顯示方向,所以為了提高可訪問性,當方向變化時,需要廣播一個可訪問性事件,說明“文本(在本例中是內容)”發生了變化。為了,需要修改setBearing方法。
具體代碼如下:
public void setBearing(float bearing) {this.bearing = bearing;sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);}
9、重寫dispatchPopulateAccessibilityEvent方法
將當前方向用作可訪問性事件使用的內容值。
具體代碼如下:
@Overridepublic boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {// TODO Auto-generated method stubsuper.dispatchPopulateAccessibilityEvent(event);if(isShown()){String bearingStr = String.valueOf(bearing);if(bearingStr.length() > AccessibilityEvent.MAX_TEXT_LENGTH){bearingStr = bearingStr.substring(0, AccessibilityEvent.MAX_TEXT_LENGTH);event.getText().add(bearingStr);return true;}return false;}return false;}
10、CompassView完整代碼
package com.lyz.compass.view;import com.lyz.compass.activity.R;import android.content.Context;import android.content.res.Resources;import android.graphics.Canvas;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;import android.view.accessibility.AccessibilityEvent;/** * 自訂View類 * @author liuyazhuang * */public class CompassView extends View {//顯示的方向private float bearing;private Paint markerPaint;private Paint textPaint;private Paint circlePaint;private String northString;private String eastString;private String southString;private String westString;private int textHeight;public CompassView(Context context){super(context);initCompassView();}public CompassView(Context context, AttributeSet attrs){super(context, attrs);initCompassView();}public CompassView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initCompassView();}/** * 初始化視圖的各個屬性 */protected void initCompassView(){setFocusable(true);Resources r = this.getResources();circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);circlePaint.setColor(r.getColor(R.color.background_color));circlePaint.setStrokeWidth(1);circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);northString = r.getString(R.string.cardinal_north);eastString = r.getString(R.string.cardinal_east);southString = r.getString(R.string.cardinal_south);westString = r.getString(R.string.cardinal_west);textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);textPaint.setColor(r.getColor(R.color.text_color));textHeight = (int) textPaint.measureText("yY");markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);markerPaint.setColor(r.getColor(R.color.marker_color));}public float getBearing() {return bearing;}public void setBearing(float bearing) {this.bearing = bearing;sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);}@Overridepublic boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {// TODO Auto-generated method stubsuper.dispatchPopulateAccessibilityEvent(event);if(isShown()){String bearingStr = String.valueOf(bearing);if(bearingStr.length() > AccessibilityEvent.MAX_TEXT_LENGTH){bearingStr = bearingStr.substring(0, AccessibilityEvent.MAX_TEXT_LENGTH);event.getText().add(bearingStr);return true;}return false;}return false;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//羅盤是一個儘可能填充更多的空間的圓,通過設定最短的邊界,高度或者寬度來設定測量的尺寸int measureWidth = measure(widthMeasureSpec);int measureHeight = measure(heightMeasureSpec);int d = Math.min(measureWidth, measureHeight);setMeasuredDimension(d, d);}/** * 解碼資料值 * @param measureSpec * @return */private int measure(int measureSpec) {int result = 0;//對測量說明進行解碼int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);//如果沒有指定界限,則返回預設大小200if(specMode == MeasureSpec.UNSPECIFIED){result = 200;}else{//由於是希望填充可用的空間,所以總是返回整個可用的邊界result = specSize;}return result;}@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stub//super.onDraw(canvas);int measuredWidth = getMeasuredWidth();int measuredHeight = getMeasuredHeight();int px = measuredWidth / 2;int py = measuredHeight / 2;//取較小的值為半徑int radius = Math.min(px, py);//繪製背景canvas.drawCircle(px, py, radius, circlePaint);canvas.save();canvas.rotate(-bearing, px, py);int textWidth = (int) textPaint.measureText("W");int cadinalX = px - textWidth / 2;int cadinalY = py - radius + textHeight;//每15度繪製一個標記,每45度繪製一個文本for(int i = 0; i < 24; i++){//繪製一個標記canvas.drawLine(px, px - radius, py, py - radius + 10, markerPaint);canvas.save();canvas.translate(0, textHeight);//繪製基本方位if(i % 6 == 0){String dirString = "";switch (i) {case 0:dirString = northString;int arrowY = 2 * textHeight;canvas.drawLine(px , arrowY, px - 5, 3 * textHeight, markerPaint);canvas.drawLine(px, arrowY, px + 5, 3 * textHeight, markerPaint);break;case 6:dirString = eastString;break;case 12:dirString = southString;break;case 18:dirString = westString;break;default:break;}canvas.drawText(dirString, cadinalX, cadinalY, textPaint);}else if(i % 3 == 0){//每45度繪製文本String angle = String.valueOf(i * 15);float angleTextWidth = textPaint.measureText(angle);int angleTextX = (int)(px - angleTextWidth/2);int angleTextY = py - radius + textHeight;canvas.drawText(angle, angleTextX, angleTextY, textPaint);}canvas.restore();canvas.rotate(15, px, py);}canvas.restore();}}
11、使用定製控制項
在MainActivity中有兩種方式引用自訂控制項,一種是引用布局檔案,一種是建立自訂控制項對象,將自訂對象設定給當前視圖。
1)引用布局檔案
package com.lyz.compass.activity;import android.app.Activity;import android.os.Bundle;import com.lyz.compass.view.CompassView;/** * 程式的主入口 * @author liuyazhuang * */public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);CompassView compassView = (CompassView) this.findViewById(R.id.compassView);compassView.setBearing(0);}}
2)建立自訂控制項對象
package com.lyz.compass.activity;import android.app.Activity;import android.os.Bundle;import com.lyz.compass.view.CompassView;/** * 程式的主入口 * @author liuyazhuang * */public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);CompassView compassView = new CompassView(this);setContentView(compassView);compassView.setBearing(0);}}
二、運行效果
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Android之——自訂羅盤視圖