Android 自定義組件之如何實現自定義組件

原文地址:http://blog.csdn.net/shineflowers/article/details/41348565


參考鏈接:http://blog.csdn.net/jjwwmlp456/article/details/41076699

簡介

Android提供了用於構建UI的強大的組件模型。兩個基類:View和ViewGroup。

可用Widget的部分名單包括Button, TextView, EditText, ListView, CheckBox,RadioButton, Gallery, Spinner,以及一些有特別作用的組件: AutoCompleteTextView, ImageSwitcher和 TextSwitcher。

可用的佈局有:LinearLayout,FrameLayout,RelativeLayout,AbsoluteLayout,GridLayout (later on api level 14 or v7-support)


基本做法

1. 繼承自View或View的子類

2. 重寫父類的一些方法,如:onDraw(),onMeasure(),onLayout()等

3. 使用自定義的組件類。


完全自定義組件

1. 最普通的作法是,繼承自View,實現你的自定義組件

2. 提供一個構造函數,採用有屬性參數的,也可以使用自定義屬性

3. 你可能想在組件中創建自己的事件監聽器,屬性訪問器和修改器,或其他行爲

4. 幾乎肯定要重寫onDraw(),onMeasure()。默認onDraw()什麼也沒作,onMeasure()則設置一個100x100的尺寸。

5. 根據需要重寫其他方法 ...


onDraw()和onMeasure()

onDraw(),提供一個Canvas,可以繪製2D圖形。

若要繪製3D圖形,請繼承GLSurfaceView,參見,api-demo下的 GLSurfaceViewActivity


onMeasure() 測量組件

1. 寬度和高度在需要測量時調用該方法

2. 應該進行測量計算組件將需要呈現的寬度和高度。它應該儘量保持傳入的規格範圍內,儘管它可以選擇超過它們(在這種情況下,父視圖可以選擇做什麼,包括裁剪,滾動,拋出一個異常,或者要求onMeasure()再次嘗試,或使用不同的測量規格)

3. 寬高計算完畢後,必須調用用setMeasuredDimession(int width, int height),進行設置。否則將拋出一個異常


下面是一些View中可被調用的方法總結(未全部包含,可自行查看類似onXxx的方法):

Category Methods Description
Creation Constructors There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.
onFinishInflate() Called after a view and all of its children has been inflated from XML.
Layout onMeasure(int, int) Called to determine the size requirements for this view and all of its children.
onLayout(boolean, int, int, int, int) Called when this view should assign a size and position to all of its children.
onSizeChanged(int, int, int, int) Called when the size of this view has changed.
Drawing onDraw(Canvas) Called when the view should render its content.
Event processing onKeyDown(int, KeyEvent) Called when a new key event occurs.
onKeyUp(int, KeyEvent) Called when a key up event occurs.
onTrackballEvent(MotionEvent) Called when a trackball motion event occurs.
onTouchEvent(MotionEvent) Called when a touch screen motion event occurs.
Focus onFocusChanged(boolean, int, Rect) Called when the view gains or loses focus.
onWindowFocusChanged(boolean) Called when the window containing the view gains or loses focus.
Attaching onAttachedToWindow() Called when the view is attached to a window.
onDetachedFromWindow() Called when the view is detached from its window.
onWindowVisibilityChanged(int) Called when the visibility of the window containing the view has changed.


自定義View示例

adi-demo下的示例:LabelView

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /* 
  2.  * Copyright (C) 2007 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package android.widget;  
  18.   
  19. import android.content.Context;  
  20. import android.graphics.Canvas;  
  21. import android.graphics.Paint;  
  22. import android.view.View;  
  23.   
  24. /** 
  25.  * Example of how to write a custom subclass of View. LabelView 
  26.  * is used to draw simple text views. Note that it does not handle 
  27.  * styled text or right-to-left writing systems. 
  28.  * 
  29.  */  
  30. public class LabelView extends View {  
  31.     /** 
  32.      * Constructor.  This version is only needed if you will be instantiating 
  33.      * the object manually (not from a layout XML file). 
  34.      * @param context the application environment 
  35.      */  
  36.     public LabelView(Context context) {  
  37.         super(context);  
  38.         initLabelView();  
  39.     }  
  40.   
  41.     /** 
  42.      * Construct object, initializing with any attributes we understand from a 
  43.      * layout file. These attributes are defined in 
  44.      * SDK/assets/res/any/classes.xml. 
  45.      *  
  46.      * @see android.view.View#View(android.content.Context, android.util.AttributeSet) 
  47.     public LabelView(Context context, AttributeSet attrs) { 
  48.         super(context, attrs); 
  49.         initLabelView(); 
  50.  
  51.         Resources.StyledAttributes a = context.obtainStyledAttributes(attrs, 
  52.                 R.styleable.LabelView); 
  53.  
  54.         CharSequence s = a.getString(R.styleable.LabelView_text); 
  55.         if (s != null) { 
  56.             setText(s.toString()); 
  57.         } 
  58.  
  59.         ColorStateList textColor = a.getColorList(R.styleable. 
  60.                                                   LabelView_textColor); 
  61.         if (textColor != null) { 
  62.             setTextColor(textColor.getDefaultColor(0)); 
  63.         } 
  64.  
  65.         int textSize = a.getInt(R.styleable.LabelView_textSize, 0); 
  66.         if (textSize > 0) { 
  67.             setTextSize(textSize); 
  68.         } 
  69.  
  70.         a.recycle(); 
  71.     } 
  72.  
  73.      */  
  74.     private void initLabelView() {  
  75.         mTextPaint = new Paint();  
  76.         mTextPaint.setAntiAlias(true);  
  77.         mTextPaint.setTextSize(16);  
  78.         mTextPaint.setColor(0xFF000000);  
  79.   
  80.         mPaddingLeft = 3;  
  81.         mPaddingTop = 3;  
  82.         mPaddingRight = 3;  
  83.         mPaddingBottom = 3;  
  84.     }  
  85.   
  86.     /** 
  87.      * Sets the text to display in this label 
  88.      * @param text The text to display. This will be drawn as one line. 
  89.      */  
  90.     public void setText(String text) {  
  91.         mText = text;  
  92.         requestLayout();  
  93.         invalidate();  
  94.     }  
  95.   
  96.     /** 
  97.      * Sets the text size for this label 
  98.      * @param size Font size 
  99.      */  
  100.     public void setTextSize(int size) {  
  101.         mTextPaint.setTextSize(size);  
  102.         requestLayout();  
  103.         invalidate();  
  104.     }  
  105.   
  106.     /** 
  107.      * Sets the text color for this label 
  108.      * @param color ARGB value for the text 
  109.      */  
  110.     public void setTextColor(int color) {  
  111.         mTextPaint.setColor(color);  
  112.         invalidate();  
  113.     }  
  114.   
  115.   
  116.     /** 
  117.      * @see android.view.View#measure(int, int) 
  118.      */  
  119.     @Override  
  120.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  121.         setMeasuredDimension(measureWidth(widthMeasureSpec),  
  122.                 measureHeight(heightMeasureSpec));  
  123.     }  
  124.   
  125.     /** 
  126.      * Determines the width of this view 
  127.      * @param measureSpec A measureSpec packed into an int 
  128.      * @return The width of the view, honoring constraints from measureSpec 
  129.      */  
  130.     private int measureWidth(int measureSpec) {  
  131.         int result;  
  132.         int specMode = MeasureSpec.getMode(measureSpec);  
  133.         int specSize = MeasureSpec.getSize(measureSpec);  
  134.   
  135.         if (specMode == MeasureSpec.EXACTLY) {  
  136.             // We were told how big to be  
  137.             result = specSize;  
  138.         } else {  
  139.             // Measure the text  
  140.             result = (int) mTextPaint.measureText(mText) + mPaddingLeft  
  141.                     + mPaddingRight;  
  142.             if (specMode == MeasureSpec.AT_MOST) {  
  143.                 // Respect AT_MOST value if that was what is called for by measureSpec  
  144.                 result = Math.min(result, specSize);  
  145.             }  
  146.         }  
  147.   
  148.         return result;  
  149.     }  
  150.   
  151.     /** 
  152.      * Determines the height of this view 
  153.      * @param measureSpec A measureSpec packed into an int 
  154.      * @return The height of the view, honoring constraints from measureSpec 
  155.      */  
  156.     private int measureHeight(int measureSpec) {  
  157.         int result;  
  158.         int specMode = MeasureSpec.getMode(measureSpec);  
  159.         int specSize = MeasureSpec.getSize(measureSpec);  
  160.   
  161.         mAscent = (int) mTextPaint.ascent();  
  162.         if (specMode == MeasureSpec.EXACTLY) {  
  163.             // We were told how big to be  
  164.             result = specSize;  
  165.         } else {  
  166.             // Measure the text (beware: ascent is a negative number)  
  167.             result = (int) (-mAscent + mTextPaint.descent()) + mPaddingTop  
  168.                     + mPaddingBottom;  
  169.             if (specMode == MeasureSpec.AT_MOST) {  
  170.                 // Respect AT_MOST value if that was what is called for by measureSpec  
  171.                 result = Math.min(result, specSize);  
  172.             }  
  173.         }  
  174.         return result;  
  175.     }  
  176.   
  177.     /** 
  178.      * Render the text 
  179.      *  
  180.      * @see android.view.View#onDraw(android.graphics.Canvas) 
  181.      */  
  182.     @Override  
  183.     protected void onDraw(Canvas canvas) {  
  184.         super.onDraw(canvas);  
  185.         canvas.drawText(mText, mPaddingLeft, mPaddingTop - mAscent, mTextPaint);  
  186.     }  
  187.   
  188.     private Paint mTextPaint;  
  189.     private String mText;  
  190.     private int mAscent;  
  191. }  

應用該自定義組件的layout xml:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.         xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"  
  3.         android:orientation="vertical"  
  4.         android:layout_width="match_parent"  
  5.         android:layout_height="wrap_content">  
  6.       
  7.     <com.example.android.apis.view.LabelView  
  8.             android:background="@drawable/red"  
  9.             android:layout_width="match_parent"  
  10.             android:layout_height="wrap_content"   
  11.             app:text="Red"/>  
  12.       
  13.     <com.example.android.apis.view.LabelView  
  14.             android:background="@drawable/blue"  
  15.             android:layout_width="match_parent"  
  16.             android:layout_height="wrap_content"   
  17.             app:text="Blue" app:textSize="20dp"/>  
  18.       
  19.     <com.example.android.apis.view.LabelView  
  20.             android:background="@drawable/green"  
  21.             android:layout_width="match_parent"  
  22.             android:layout_height="wrap_content"   
  23.             app:text="Green" app:textColor="#ffffffff" />  
  24.   
  25. </LinearLayout>  

該示例演示了:

1. 繼承自View的完全自定義組件

2. 帶參數的構造函數(一些屬性參數在xml中設置)。還使用了自定義屬性 R.styleable.LabelView

3. 一些標準的public 方法,如setText()、setTextSize()、setTextColor()

4. onMeasure()測量組件尺寸,內部由measureWidth(int measureSpec) 和 measureHeight(int measureSpec)來測量。

5. onDraw()將Label繪製到畫面Canvas上


複合組件

由一些現有組件,複合成一個新的組件。
要創建一個複合組件:
1. 通常需要創建一個類,繼承自一個Layout,或者ViewGroup。
2. 在構造函數中,需要先調用父類相應的構造函數。然後設置一些需要的組件用於複合。可以使用自定義屬性
3. 可以創建監聽器,監聽處理一些可能的動作
4. 可能有一些 屬性的 get / set 方法
5. 如果繼承自某一Layout類時,不需要重寫onDraw()和onMeasure(),因爲Layout類中有默認的行爲。如有必要,當然也可以重寫
6. 可能重寫其他一些onXxx(),以達到你想要的效果

複合組件示例

api-demo下的List4和List6裏的內部類SpeachView,以下爲List6中的源碼
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private class SpeechView extends LinearLayout {  
  2.        public SpeechView(Context context, String title, String dialogue, boolean expanded) {  
  3.            super(context);  
  4.              
  5.            this.setOrientation(VERTICAL);  
  6.              
  7.            // Here we build the child views in code. They could also have  
  8.            // been specified in an XML file.  
  9.              
  10.            mTitle = new TextView(context);  
  11.            mTitle.setText(title);  
  12.            addView(mTitle, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));  
  13.              
  14.            mDialogue = new TextView(context);  
  15.            mDialogue.setText(dialogue);  
  16.            addView(mDialogue, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));  
  17.              
  18.            mDialogue.setVisibility(expanded ? VISIBLE : GONE);  
  19.        }  
  20.          
  21.        /** 
  22.         * Convenience method to set the title of a SpeechView 
  23.         */  
  24.        public void setTitle(String title) {  
  25.            mTitle.setText(title);  
  26.        }  
  27.          
  28.        /** 
  29.         * Convenience method to set the dialogue of a SpeechView 
  30.         */  
  31.        public void setDialogue(String words) {  
  32.            mDialogue.setText(words);  
  33.        }  
  34.          
  35.        /** 
  36.         * Convenience method to expand or hide the dialogue 
  37.         */  
  38.        public void setExpanded(boolean expanded) {//該方法在List4中沒有  
  39.            mDialogue.setVisibility(expanded ? VISIBLE : GONE);  
  40.        }  
  41.          
  42.        private TextView mTitle;  
  43.        private TextView mDialogue;  
  44.    }  
SpeachView,繼承了LinearLayout,縱向佈局。內部有一個TextView的title,一個TextView的dialogue。List4完全展開兩個TextView;List6點擊title可以收縮/展開dialogue。

修改現有View類型

繼承自一個現有的View,以增強其功能,滿足需要。

sdk中有個記事本NotePad的示例工程。其中有一個類就是擴展了EditText。
在NoteEditor類中:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public static class LinedEditText extends EditText {  
  2.        private Rect mRect;  
  3.        private Paint mPaint;  
  4.   
  5.        // This constructor is used by LayoutInflater  
  6.        public LinedEditText(Context context, AttributeSet attrs) {  
  7.            super(context, attrs);  
  8.   
  9.            // Creates a Rect and a Paint object, and sets the style and color of the Paint object.  
  10.            mRect = new Rect();  
  11.            mPaint = new Paint();  
  12.            mPaint.setStyle(Paint.Style.STROKE);  
  13.            mPaint.setColor(0x800000FF);  
  14.        }  
  15.   
  16.        /** 
  17.         * This is called to draw the LinedEditText object 
  18.         * @param canvas The canvas on which the background is drawn. 
  19.         */  
  20.        @Override  
  21.        protected void onDraw(Canvas canvas) {  
  22.   
  23.            // Gets the number of lines of text in the View.  
  24.            int count = getLineCount(); //edittext中有幾行,  edittext繼承textview  
  25.   
  26.            // Gets the global Rect and Paint objects  
  27.            Rect r = mRect;  
  28.            Paint paint = mPaint;  
  29.   
  30.            /* 
  31.             * Draws one line in the rectangle for every line of text in the EditText 
  32.             */  
  33.            for (int i = 0; i < count; i++) {  
  34.   
  35.                // Gets the baseline coordinates for the current line of text  
  36.                int baseline = getLineBounds(i, r);//將一行的範圍座標賦給矩形r;返回一行y方向上的基準線座標  
  37.   
  38.                /* 
  39.                 * Draws a line in the background from the left of the rectangle to the right, 
  40.                 * at a vertical position one dip below the baseline, using the "paint" object 
  41.                 * for details. 
  42.                 */  
  43.                canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);//繪製一條線,寬度爲原行的寬度,高度爲從基線開始+1個像素  
  44.            }  
  45.   
  46.            // Finishes up by calling the parent method  
  47.            super.onDraw(canvas);  
  48.        }  
  49.    }  


定義

一個public的靜態內部類,以便它可以被訪問:NoteEditor.MyEditText
它是靜態內部類,意味着,它不依靠外部類的成員,不會產生一些“組合的方法”。
繼承自EditText

類的初始化

構造函數中,先調用父類的構造方法,並且它是帶屬性參數的構造函數。在使用時,從一個xml佈局文件inflate

重寫的方法

只有onDraw()被重寫。在onDraw()中繪製了一條藍色的線,該線從每行文本的的基線開始向下1像素,寬度爲行寬。
方法結束前,調用super.onDraw()

使用自定義組件

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <view xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     class="com.example.android.notepad.NoteEditor$LinedEditText"  
  3.     android:id="@+id/note"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:background="@android:color/transparent"  
  7.     android:padding="5dp"  
  8.     android:scrollbars="vertical"  
  9.     android:fadingEdge="vertical"  
  10.     android:gravity="top"  
  11.     android:textSize="22sp"  
  12.     android:capitalize="sentences"  
  13. />  

使用完全限定類名,引入自定義組件。使用$引用內部類。
發佈了53 篇原創文章 · 獲贊 16 · 訪問量 75萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章