效果圖:
這是建行APP的一個界面,最近在學習自定義view,所以記錄一下心得。
分析:
① 因為該view里面可以盛放很多子view,所以要繼承ViewGroup,而不是view。
② view中的子孩子數據是不確定的,也就是從服務器上請求下來的數據,所以我們在使用該view的時候,要進行設置數據的操作。
③ 當使用者設置數據,肯定是一組中必須要有一個text和一個ImageView這兩個數據,那么這個是確定的,所以我們可以先寫出子view的xml,通過遍歷傳遞過來的數據,一個個將子view 添加到viewGroup中。
④ 將子view添加到viewGroup中,要對子view進行測量以及擺放操作。
⑤測量,也就是重寫onMeasure方法,需要注意的幾點:
a.只是測量自身,不會測量子孩子。
b.MeasureSpec 是由32位int組成的,前兩位是數據模式Mode,剩余是數據大小Size。
MeasureSpec = size + mode
c.其中Mode分為三種:
UNSPECIFIED數值為0<<30(后30位)
EXACTLY數值為1<<30, 明確的尺寸,如XXXdp,MATCH_PARENT
AT_MOST數值為2<<30,至多為多少,如WRAP_CONTENT,若控件本身沒有默認尺寸,則系統會盡可能的把空間賦予控件,為MATCH_PARENT ~~~~~《注意這點》
⑥布局排列 onLayout
確定子view的位置,左上右下,在計算的時候,會使用到角度值,注意該角度對應每一個view的變化。
onMeasure方法中常用API:
setMeasureDimension(width,height):設置控件的最終尺寸
MeasureSpec spec= MeasureSpec.makeMeasureSpec(size,mode);用于指定MeasureSpec
MeasureSpec.getSize(measureSpec);通過MeasureSpec獲取size
MeasureSpec.getMode(measureSpec);通過MeasureSpec獲取mode
getSuggestedMinimumWidth():獲得背景圖的寬度,如果沒有背景,則返回值為0
····
開擼:
1·創建一個類,繼承ViewGroup,實現構造:
public class MenuView extends ViewGroup {
private String TAG = "MenuView";
public MenuView(Context context) {
this(context,null);
}
public MenuView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MenuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
2.xml布局中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorWhite3"
android:gravity="center"
android:orientation="vertical">
<zyh.demo.view.MenuView
android:id="@+id/frg_circle_mv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/circle_bg"/>
</LinearLayout>
3.調用者中使用
private String[] texts = new String[]{"安全", "服務", "理財",
"匯款", "賬戶", "信用"};
private int[] imgs = new int[]{R.drawable.home_mbank_1_normal,
R.drawable.home_mbank_2_normal, R.drawable.home_mbank_3_normal,
R.drawable.home_mbank_4_normal, R.drawable.home_mbank_5_normal,
R.drawable.home_mbank_6_normal};
.....省略部分代碼......
View view = inflater.inflate(R.layout.frg_circle, null, false);
MenuView menuView = (MenuView) view.findViewById(R.id.frg_circle_mv);
//給viewGroup設置數據,當然這個數據是模擬的假數據,不過要保證這兩組數據要保持一致,不然會報角標越界
menuView.setData(texts,imgs);
4.對MenuView進行數據處理
public void setData(String[] texts, int[] imgs) {
for (int i = 0; i <texts.length ; i++) {
// 遍歷使用者傳遞過來的數據,創建一個子view視圖,并轉換成view。
View view = View.inflate(getContext(), R.layout.view_menu, null);
TextView textView = (TextView) view.findViewById(R.id.menu_tv);
ImageView imageView = (ImageView) view.findViewById(R.id.menu_iv);
// 設置數據
textView.setText(texts[i]);
imageView.setImageResource(imgs[i]);
//添加到viewGroup中
addView(view);
}
}
子view視圖
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="10dp"
android:layout_height="10dp">
<TextView
android:id="@+id/menu_tv"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="sss"
android:textColor="@color/colorWhite"/>
<ImageView
android:id="@+id/menu_iv"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/home_mbank_1_normal"/>
</LinearLayout>
5.重寫onLayout方法
private int d = 350; //初次定義背景的直徑,后期會有改動
/**弧度*/
private int stratAngle = 0;
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
for (int j = 0; j < getChildCount(); j++) {
View childView = getChildAt(j);
// 計算子view的寬度
int childWidth = childView.getMeasuredWidth();
// 自定義控件所在的圓到子視圖之間的距離。
float temp = d / 3.0f;
// 動態的計算子view的左上右下
int left = (int) (d /2 + Math.round(temp * Math.cos(Math.toRadians(stratAngle))) - childWidth/2);
int right = left + childWidth;
int top = (int) (d / 2 + Math.round(temp * Math.sin(Math.toRadians(stratAngle))) - childWidth / 2);
int buttom = top + childWidth;
// 確定view布局的位置
childView.layout(left,top,right,buttom);
//stratAngle弧度是一個累加過程,不是固定的值
stratAngle += 360 / getChildCount();
}
}
6.重寫onMeasure方法,如果不重寫該方法的話,會導致看不到子view,因為沒有測量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 測量父布局
//android:gravity="center"
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
//獲取測量模式及測量的大小;
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
// 判斷測量模式
if(mode != MeasureSpec.EXACTLY) {
// 這個是不確定的值,最大為 wrap_content
// 有沒有背景?
//獲取背景
int suggestedMinimumWidth = getSuggestedMinimumWidth();
if (suggestedMinimumWidth == 0) { // 沒有背景圖
//默認寬度就是屏幕大小
measuredHeight = measuredWidth = getDefaultWidth();
}else {
measuredHeight = measuredWidth = Math.min(suggestedMinimumWidth,getDefaultWidth());
}
}else{ // 固定的數據,match_parent 或者 xxxdp
//如果是填充父窗體,也就是屏幕的寬高的較小值,《保證在屏幕可見范圍內》
// 如果是固定的數值的話,也就是size,如果用戶傳入大無窮的數據,也是不可見的,所以屏幕的大小是一個界限。最大不過屏幕的大小
measuredHeight = measuredWidth = Math.min(size,getDefaultWidth());
}
d = measuredHeight;
//設置控件的最終尺寸
Log.e(TAG,"measuredHeight:"+measuredHeight);
setMeasuredDimension(measuredWidth,measuredHeight); //最終目的就是它~~~~~·
//遍歷所有的子孩子,進行測量
for (int i = 0; i < getChildCount(); i++) {
View childAt = getChildAt(i);
// 對子孩子指定MeasureSpec,一定要指定子孩子的MeasureSpec,否則子孩子無法測量,進而不知道子孩子的擺放位置。
int makeMeasureSpec = MeasureSpec.makeMeasureSpec(d / 5, MeasureSpec.EXACTLY);
childAt.measure(makeMeasureSpec,makeMeasureSpec);
}
}
/**
* 獲取屏幕的寬高的較小值
* */
private int getDefaultWidth (){
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
return Math.min(width,height);
}
這樣一步步的,自定義view就實現了,明個周六,又可以休息啦~~~~·加油 !!!