Android之自定义View的实现
對于學習Android開發的小童鞋對于自定義View一定不會陌生,相信大家對它是又愛又恨,愛它可以跟隨我們的心意設計出漂亮的效果;恨它想要完全流暢掌握,需要一定的功夫。對于初學者來說確實很不容易,網上有很多相關的博客文檔之類的資料,但是往往對于初學者,只知拷貝修改,不理解其中的深意,最后還是無法更具自己的需要,進行自定義的開發,本篇我們就一同來了解下自定義View的神秘面紗。
開發自定義控件的步驟:
1、了解View的工作原理? 2、 編寫繼承自View的子類 3、 為自定義View類增加屬性? 4、 繪制控件? 5、 響應用戶消息? 6 、自定義回調函數? 一、View結構原理 Android系統的視圖結構的設計也采用了組合模式,即View作為所有圖形的基類,Viewgroup對View繼承擴展為視圖容器類。 View定義了繪圖的基本操作 基本操作由三個函數完成:measure()、layout()、draw(),其內部又分別包含了onMeasure()、onLayout()、onDraw()三個子方法。具體操作如下: 1、measure操作 measure操作主要用于計算視圖的大小,即視圖的寬度和長度。在view中定義為final類型,要求子類不能修改。measure()函數中又會調用下面的函數: (1)onMeasure(),視圖大小的將在這里最終確定,也就是說measure只是對onMeasure的一個包裝,子類可以覆寫onMeasure()方法實現自己的計算視圖大小的方式,并通過setMeasuredDimension(width, height)保存計算結果。 2、layout操作 layout操作用于設置視圖在屏幕中顯示的位置。在view中定義為final類型,要求子類不能修改。layout()函數中有兩個基本操作: (1)setFrame(l,t,r,b),l,t,r,b即子視圖在父視圖中的具體位置,該函數用于將這些參數保存起來; (2)onLayout(),在View中這個函數什么都不會做,提供該函數主要是為viewGroup類型布局子視圖用的; 3、draw操作 draw操作利用前兩部得到的參數,將視圖顯示在屏幕上,到這里也就完成了整個的視圖繪制工作。子類也不應該修改該方法,因為其內部定義了繪圖的基本操作: (1)繪制背景; (2)如果要視圖顯示漸變框,這里會做一些準備工作; (3)繪制視圖本身,即調用onDraw()函數。在view中onDraw()是個空函數,也就是說具體的視圖都要覆寫該函數來實現自己的顯示(比如TextView在這里實現了繪制文字的過程)。而對于ViewGroup則不需要實現該函數,因為作為容器是“沒有內容“的,其包含了多個子view,而子View已經實現了自己的繪制方法,因此只需要告訴子view繪制自己就可以了,也就是下面的dispatchDraw()方法; (4)繪制子視圖,即dispatchDraw()函數。在view中這是個空函數,具體的視圖不需要實現該方法,它是專門為容器類準備的,也就是容器類必須實現該方法; (5)如果需要(應用程序調用了setVerticalFadingEdge或者setHorizontalFadingEdge),開始繪制漸變框; (6)繪制滾動條; 從上面可以看出自定義View需要最少覆寫onMeasure()和onDraw()兩個方法。 二、View類的構造方法 創建自定義控件的3種主要實現方式: 1)繼承已有的控件來實現自定義控件: 主要是當要實現的控件和已有的控件在很多方面比較類似, 通過對已有控件的擴展來滿足要求。 2)通過繼承一個布局文件實現自定義控件,一般來說做組合控件時可以通過這個方式來實現。 注意此時不用onDraw方法,在構造廣告中通過inflater加載自定義控件的布局文件,再addView(view),自定義控件的圖形界面就加載進來了。 3)通過繼承view類來實現自定義控件,使用GDI繪制出組件界面,一般無法通過上述兩種方式來實現時用該方式。 三、自定義View增加屬性的兩種方法: 1)在View類中定義。通過構造函數中引入的AttributeSet 去查找XML布局的屬性名稱,然后找到它對應引用的資源ID去找值。 案例:實現一個帶文字的圖片(圖片、文字是onDraw方法重繪實現) /*** 當實現自定義View時,需要重寫它的三個構造方法*/ public class MySelfView extends View{private String mtext;//文本內容private int msrc;//圖片系統內地址public MySelfView(Context context) {this(context, null, 0);}public MySelfView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MySelfView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context, attrs);}//初始化操作private void init(Context context, AttributeSet attrs) {int textId = attrs.getAttributeResourceValue(null, "Text", 0);int srcId = attrs.getAttributeResourceValue(null, "Src", 0);mtext = context.getResources().getText(textId).toString();msrc = srcId;}@Overrideprotected void onDraw(Canvas canvas) {//canvas:畫板對象super.onDraw(canvas);Paint paint = new Paint();//畫筆對象paint.setColor(Color.BLUE);//設置畫筆的顏色InputStream is = getResources().openRawResource(msrc);Bitmap mBitmap = BitmapFactory.decodeStream(is);int bh = mBitmap.getHeight();//獲取圖片的高int bw = mBitmap.getWidth();//獲取圖片的寬canvas.drawBitmap(mBitmap, 100, 100, paint);//繪制圖片canvas.drawCircle(50, 50, 50, paint);//繪制圖形canvas.drawText(mtext, bw/2, 30, paint);//繪制文字 }}布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="${relativePackage}.${activityClass}" ><com.example.myselfview.view.MySelfViewandroid:layout_width="wrap_content"android:layout_height="wrap_content" Text="@string/hello_world"Src="@drawable/ic_launcher" /></LinearLayout>屬性Text, Src在自定義View類的構造方法中讀取。
最終的顯示效果:
2)通過XML為View注冊屬性。與Android提供的標準屬性寫法一樣。 1、自定義View的屬性,首先在res/values/ ?下建立一個attrs.xml , 在里面定義我們的屬性和聲明我們的整個樣式。 <?xml version="1.0" encoding="utf-8"?> <resources><attr name="TitleText" format="string" /><attr name="TitleColor" format="color" /><attr name="TitleSize" format="dimension" /><declare-styleable name="CustomTitleView"><attr name="TitleText" /> <attr name="TitleColor" /> <attr name="TitleSize" /></declare-styleable></resources>
我們定義了字體,字體顏色,字體大小3個屬性,format是值該屬性的取值類型:一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;不清楚的可以google一把。
2、在View的構造方法中,獲得我們的自定義的樣式
public class MySelfViewRandomNumber extends View{/** * 文本 */ private String mTitleText;/** * 文本的顏色 */ private int mTitleTextColor;/** * 文本的大小 */ private int mTitleTextSize;/** * 繪制時控制文本繪制的范圍 */ private Rect mBound;//設置文本繪制的范圍 private Paint mPaint;//畫筆對象public MySelfViewRandomNumber(Context context) {this(context, null, 0);}public MySelfViewRandomNumber(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MySelfViewRandomNumber(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);/** * 獲得我們所定義的自定義樣式屬性 */ TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyleAttr, 0); int n = typedArray.getIndexCount(); for (int i = 0; i < n; i++) {int attr = typedArray.getIndex(i); switch (attr) { case R.styleable.CustomTitleView_TitleText:mTitleText = typedArray.getString(attr);break; case R.styleable.CustomTitleView_TitleColor:// 默認顏色設置為黑色 mTitleTextColor = typedArray.getColor(attr, Color.BLACK);break;case R.styleable.CustomTitleView_TitleSize:// 默認設置為16sp,TypeValue也可以把sp轉化為px mTitleTextSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; } } typedArray.recycle(); /** * 獲得繪制文本的寬和高 */ mPaint = new Paint(); mPaint.setTextSize(mTitleTextSize); mPaint.setColor(mTitleTextColor); mBound = new Rect(); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);//添加點擊事件this.setOnClickListener(new OnClickListener() {@Override public void onClick(View v) { mTitleText = randomText(); postInvalidate();//更新View視圖 } });}//生成隨機數private String randomText() { Random random = new Random(); Set<Integer> set = new HashSet<Integer>(); while (set.size() < 4) { int randomInt = random.nextInt(10); set.add(randomInt); } StringBuffer sb = new StringBuffer(); for (Integer i : set) { sb.append("" + i); } return sb.toString(); }@Override //獲得并設置控件的寬和高protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else { mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound); float textWidth = mBound.width(); int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight()); width = desired; } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize;} else { mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound); float textHeight = mBound.height(); int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom()); height = desired; } setMeasuredDimension(width, height);} @Override //繪制控件protected void onDraw(Canvas canvas) { mPaint.setColor(Color.YELLOW); canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); mPaint.setColor(mTitleTextColor); canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint); }}我們重寫了3個構造方法,默認的布局文件調用的是兩個參數的構造方法,所以記得讓所有的構造調用我們的三個參數的構造,我們在三個參數的構造中獲得自定義屬性。
系統幫我們測量的高度和寬度都是MATCH_PARNET,當我們設置明確的寬度和高度時,系統幫我們測量的結果就是我們設置的結果,當我們設置為WRAP_CONTENT,或者MATCH_PARENT系統幫我們測量的結果就是MATCH_PARENT的長度。
所以,當設置了WRAP_CONTENT時,我們需要自己進行測量,即重寫onMesure方法”:
重寫之前先了解MeasureSpec的specMode,一共三種類型:
EXACTLY:一般是設置了明確的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一個最大值內,一般為WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是我們重寫onMeasure代碼:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height ; if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else { mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds); float textWidth = mBounds.width(); int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight()); width = desired; } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else { mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds); float textHeight = mBounds.height(); int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom()); height = desired; } setMeasuredDimension(width, height); }2、然后在布局中聲明我們的自定義View
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:custom="http://schemas.android.com/apk/res/com.example.myselfview"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><com.example.myselfview.view.MySelfViewRandomNumberandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_height="wrap_content" android:padding="10dp"android:layout_centerInParent="true"custom:TitleText="3254"custom:TitleColor="#ff0000"custom:TitleSize="16sp"/></RelativeLayout>一定要引入 xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"我們的命名空間,后面的包路徑指的是項目的package
附上我們的運行效果圖:
關于自定義View的設計與學習就先總結到這里,本篇僅僅為了入門了解,對于自定義View還有很多的知識需要學習,希望在接下來的時間,大家多多交流,相互學習。
轉載于:https://www.cnblogs.com/AndroidJotting/p/5455482.html
總結
以上是生活随笔為你收集整理的Android之自定义View的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在 GridView 控件中添加一列复选
- 下一篇: Android深度探索读书笔记 第六章