組合控件,顧名思義,多個控件組合成一個控件使用。好比,咱們想要這樣一個ImageView,圖片的底部覆蓋一個浮層,浮層上面顯示一行文字,這個控件咱們能夠用TextView覆蓋在ImageView之上實現,咱們把這個控件命名爲「CoverImageView」吧。java
既然是組合,那麼就須要一個容器把這些分散的控件裝在一塊兒,這個容器就是ViewGroup,如:LinearLayout、RelativeLayout等,因此組合控件都是繼承於一個ViewGroup的。android
組裝的方式有兩種:git
第一種方式可以比較直觀的看到組合效果,可是因爲須要解析xml,因此性能上稍微差點;第二種並不能直觀的看到效果,可是性能稍好。對於新手來講,能夠先用第一種方式實現,等到對組合控件的實現比較熟悉後可使用第二種方式實現。github
能實現覆蓋效果的ViewGroup有RelativeLayout、FrameLayout,咱們以FrameLayout爲例。app
xml佈局以下:函數
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <!--圖片佔據整個控件大小--> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:scaleType="centerCrop" android:src="@drawable/demo"/> <!--文字與控件等寬、在整個控件的底部--> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="#64000000" android:gravity="center" android:padding="6dp" android:text="@string/laomengzhu" android:textColor="#ffffff"/> </FrameLayout>
效果以下:工具
xml已經定義好了,咱們把它佈局到咱們的ViewGroup裏面去。咱們的控件「CoverImageView」繼承於FrameLayout,咱們實現一個「setupViews」函數,用來初始化View,並在全部構造函數裏面調用這個函數。佈局
public class CoverImageView extends FrameLayout { public CoverImageView(Context context) { super(context); setupViews(context, null); } public CoverImageView(Context context, AttributeSet attrs) { super(context, attrs); setupViews(context, attrs); } public CoverImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setupViews(context, attrs); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CoverImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setupViews(context, attrs); } private void setupViews(Context context, AttributeSet attrs) { //將xml佈局到ViewGroup裏面來 View.inflate(context, R.layout.layout_civ, this); } }
如今咱們能夠把這個控件放到咱們的Activity裏面看看效果了:性能
前面咱們已經看到咱們想要的效果了,可是咱們在xml裏面的根佈局是FrameLayout,咱們的CoverImageView也是繼承於FrameLayout,那咱們最終實現的效果會不會套了兩層FrameLayout呢?優化
咱們可使用ADT裏面的Hierarchyviewer工具查看佈局層級關係,使用方法也很簡單:
咱們當前頁面的部分層級關係以下圖:
能夠看到CoverImage下面的確還有一層FrameLayout,這一層FrameLayout是毫無用處的,這樣的層級關係就會致使過分繪製問題(不懂的能夠百度一下,這裏就不贅述了),影響咱們的控件性能。
減小視圖層級,咱們可使用merge標籤,它能夠刪減多餘的層級,優化UI。咱們把咱們xml佈局裏面的FrameLayout換成merge,而後運行,看看視圖層級關係。
層級減小了!!!
關於佈局優化,你們能夠看看這個博客:http://blog.csdn.net/xyz_lmn/article/details/14524567
前面講到,組裝控件的另外一個方式就是經過代碼添加控件,原理也很簡單,就是在咱們的ViewGroup裏面直接建立子控件、而後添加到咱們的ViewGroup裏,調整子控件的佈局,達到咱們想要的效果。因爲少了xml解析這一步(雖然你沒有解析,可是系統須要解析的),因此性能是優於第一種的。
private void setupViews(Context context, AttributeSet attrs) { //將xml佈局到ViewGroup裏面來 /*View.inflate(context, R.layout.layout_civ, this);*/ //經過代碼添加子控件 LayoutParams lp; ImageView imageView = new ImageView(context); //設置縮放模式 imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setImageResource(R.drawable.demo); //設置子控件佈局參數 lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); //將子控件加入ViewGroup裏 addView(imageView, lp); TextView textView = new TextView(context); //設置內邊距 int padding; if (isInEditMode()) { padding = 18; } else { padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, getResources().getDisplayMetrics()); } textView.setPadding(padding, padding, padding, padding); textView.setBackgroundColor(Color.parseColor("#64000000")); textView.setTextColor(Color.WHITE); //文字居中 textView.setGravity(Gravity.CENTER); textView.setText(R.string.laomengzhu); lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.BOTTOM; addView(textView, lp); }
效果和上面徹底一致。
前面咱們實現了咱們想要的效果,可是咱們沒法修改控件裏面的圖片、文字大小,咱們須要定義控件的這些屬性,固然你還能夠定義文字顏色、文字背景色等屬性,咱們這裏只挑圖片、文字大小以做演示。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CoverImageView"> <!--name:屬性名稱 format:屬性格式--> <!--屬性支持的格式有: 1. reference:參考某一資源ID。 2. color:顏色值。 3. boolean:布爾值。 4. dimension:尺寸值。 5. float:浮點值。 6. integer:整型值。 7. string:字符串。 8. fraction:百分數。 9. enum:枚舉值。 10. flag:位或運算。--> <attr name="imgSrc" format="reference"/> <attr name="coverTextSize" format="dimension|reference"/> </declare-styleable> </resources>
而後咱們須要在控件中解析屬性:
//解析控件屬性 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CoverImageView); //解析圖片資源ID int resId = ta.getResourceId(R.styleable.CoverImageView_imgSrc, -1); if (resId != -1) { imageView.setImageResource(resId); } //解析文字大小 resId = ta.getResourceId(R.styleable.CoverImageView_coverTextSize, -1); int textSize = 0; if (resId != -1) { if (!isInEditMode()) { textSize = getResources().getDimensionPixelSize(resId); } } else { textSize = ta.getDimensionPixelSize(R.styleable.CoverImageView_coverTextSize, 0); } if (textSize > 0) { textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } //釋放 ta.recycle();
使用屬性:
<com.laomengzhu.civ.CoverImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/view" app:imgSrc="@drawable/demo1" app:coverTextSize="18sp" android:layout_centerInParent="true"/>
自定義控件的屬性默認是在xmlns:app="http://schemas.android.com/apk/res-auto"命名空間下的。
到此,咱們自定義組合控件就完成了,最終效果以下圖:
代碼:https://github.com/laomengzhu/CustomWidget/tree/master/CombinationWidget/CoverImageView