android自定義最簡單的流式佈局

一、.首先 在自定義view方面需要 android 最基礎的知識也就是View的繪製流程了

 measure、layout、draw的三個執行流程

measure:測量,測量自己有多大,如果是ViewGroup的話會同時測量裏面的子控件的大小
    layout:擺放裏面的子控件
    draw:繪製 (重寫onDraw)
 

MeasureSpec:測量規格
int 32位:010111100011100
拿前面兩位當做mode,後面30位當做值。
    1.mode:
        1) EXACTLY: 精確的。比如給了一個確定的值 20dp(Match_parent)
        2)  AT_MOST: 根據父容器當前的大小,結合你指定的尺寸參考值來考慮你應該是多大尺寸,需要計算(wrap_content就是屬於這種)
        3)  UNSPECIFIED: 最多的意思。根據當前的情況,結合你制定的尺寸參考值來考慮,在不超過父容器給你限定的只存的前提下,來測量你的一個恰好的內容尺寸。   用的比較少,一般見於ScrollView,ListView(大小不確定,同時大小還是變的。會通過多次測量才能真正決定好寬高。)
    2.value:寬高的值。

經過大量測量以後,最終確定了自己的寬高,需要調用:setMeasuredDimension(w,h)

寫自定義控件的時候,我們要去獲得自己的寬高來進行一些計算,必須先經過measure,才能獲得到寬高---不是getWidth(),而是getMeasuredWidth()
也就是當我們重寫onMeasure的時候,我們需要在裏面調用child.measure()才能獲取child的寬高。

從規格當中獲取mode和value:
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
反過來將mode和value合成一個規格呢:
    MeasureSpec.makeMeasureSpec(resultSize, resultMode);

總結

玩自定義viewGroup需要記住以下幾點 就可以很好的做好自定義控件了

//1.測量自己的尺寸
        ViewGroup.onMeasure();
            //1.1 爲每一個child計算測量規格信息(MeasureSpec)
            ViewGroup.getChildMeasureSpec();
            //1.2 將上面測量後的結果,傳給每一個子View,子view測量自己的尺寸
            child.measure();

            //1.3 子View測量完,ViewGroup就可以拿到這個子View的測量後的尺寸了
            child.getChildMeasuredSize();//child.getMeasuredWidth()和child.getMeasuredHeight()
            //1.4ViewGroup自己就可以根據自身的情況(Padding等等),來計算自己的尺寸
            ViewGroup.calculateSelfSize();
        //2.保存自己的尺寸
        ViewGroup.setMeasuredDimension(size);

 

以下案例:


自定義viewGroup

package com.roy.widgetdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class CustomLayout extends ViewGroup {
    private static int spac = 100;
    public CustomLayout(Context context) {
        this(context,null);
    }

    public CustomLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomLayout, defStyleAttr, 0);
        for (int i = 0; i < typedArray.length(); i++) {
            int index = typedArray.getIndex(i);
            switch (index){
                case R.styleable.CustomLayout_horizontal_spacing:
                    spac = typedArray.getDimensionPixelSize(index,spac);
                    break;
            }
        }
        typedArray.recycle();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int top =0;
        int bottom = 0;
        int left = 0;
        int right =0 ;
        int lineFeedIndex =0;
        for (int i = 0; i < childCount; i++) {
            View childAt = getChildAt(i);
            if(i!=0){
                View previousView =  getChildAt(i-1);  //上一個view
                left = left+ spac  + previousView.getMeasuredWidth() ;
            }

            right = left + childAt.getMeasuredWidth();
            if(right>getMeasuredWidth()){
                lineFeedIndex = 0;
                top = top + childAt.getMeasuredHeight();
                left = 0;
            }
            bottom = top + childAt.getMeasuredHeight();
             childAt.layout(left,top,right,bottom);  //位置擺放
            lineFeedIndex ++ ;
        }
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
       int widthMeasure = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightMeasure = MeasureSpec.getSize(heightMeasureSpec);
        //測量子view
           int count = getChildCount(); //獲取子view的個數
        for (int i = 0; i < count; i++) {
           View  view = getChildAt(i);  //獲取子view
            LayoutParams layoutParams = view.getLayoutParams();
            int childWidthMeasureSpec =  getChildMeasureSpec(widthMeasureSpec,0,layoutParams.width);     //獲取子view測量規格
            int childHeightMeaureSpec = getChildMeasureSpec(heightMeasureSpec,0,layoutParams.height);
            view.measure(childWidthMeasureSpec,childHeightMeaureSpec); //測量子view
        }

        int width =0;
        int height =0;
        int childCount = getChildCount();
        //開始測量自己
        switch (widthMode){
            case MeasureSpec.EXACTLY:
                 width = widthMeasure;   //父容器多大我就多大
                break;
                case MeasureSpec.AT_MOST:
                case MeasureSpec.UNSPECIFIED:

                    for (int i = 0; i < childCount; i++) {
                         View view =  getChildAt(i);
                        width = width+ i*spac + view.getMeasuredWidth();
                    }
                    if(width >widthMeasure){
                        width = widthMeasure;
                    }
                        break;
        }

        switch (heightMode){
            case MeasureSpec.EXACTLY:
                height = heightMeasure;   //父容器多大我就多大
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View view =  getChildAt(i);
                    height = height + view.getMeasuredHeight();
                }
                break;
        }
        setMeasuredDimension(width,height); //保存測量的值
    }


    public void setAdapter(BaseAdapter baseAdapter){
             if(baseAdapter !=null){
                 for (int i = 0; i < baseAdapter.getCount(); i++) {
                    View child =    baseAdapter.getView(i,null,this);
                       addView(child);
                 }

                 if (getChildCount() > 0) {
                     requestLayout();
                     invalidate();
                 }
             }
    }
}

attrs文件 在佈局可以通過 以下屬性設置間距

 <declare-styleable name="CustomLayout">
        <attr name="horizontal_spacing" format="dimension" />

    </declare-styleable>

drawable 文件  可以給view設置樣式

<shape xmlns:android="http://schemas.android.com/apk/res/android">
  <corners android:radius="5dp"/>
    <stroke android:color="@color/colorAccent" android:width="2dp"/>
   <solid android:color="#fff"/>
</shape>

子view的話就不自己自定義一個了 就利用BaseAdapter類添加view了

public abstract class CommonAdapter<T> extends BaseAdapter{
	protected Context mContext;
	protected List<T> mDatas;
	protected LayoutInflater mInflater;
	protected int mLayoutIds;
	
	
	public CommonAdapter(Context context, List<T> datas, int LayoutIds) {
		this.mContext = context;
		mInflater = LayoutInflater.from(context);
		this.mDatas = datas;
		this.mLayoutIds = LayoutIds;

	}
	
	@Override
	public int getCount() {
		
		return mDatas.size();
	}
	
	@Override
	public T getItem(int position) {
		
		return mDatas.get(position);
	}
	
	public void refreshData(List<T> datas){
		this.mDatas = datas;
		notifyDataSetChanged();
	}

	@Override
	public long getItemId(int position) {
		
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent){
		ViewHolder holder = ViewHolder.get(mContext, convertView, parent, mLayoutIds, position);
		convert(holder, getItem(position), position);
		return holder.getConvertView();
	}


	public abstract void convert(ViewHolder holder, T t, int pos);
}

所需要的ViewHolder

package com.roy.widgetdemo;

import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;

public class ViewHolder {
	
	private SparseArray<View> mViews;
	private int mPosition;
	private View mConvertView;
	
	public ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
		this.mPosition = position;
		this.mViews = new SparseArray<View>();
		this.mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
		mConvertView.setTag(this);
	}
	
	public static ViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position){
		
		if (convertView == null) {
			
			return new ViewHolder(context, parent, layoutId, position);
		}else {
			ViewHolder holder = (ViewHolder) convertView.getTag();
			holder.mPosition = position;
			return holder;
		}		
	}
	/**
	 * 通過viewId獲取控件
	 * @param viewId
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T extends View> T getView(int viewId){
		View view = mViews.get(viewId);
		if (view == null) {
			view = mConvertView.findViewById(viewId);
			mViews.put(viewId, view);
		}
		Log.d("debug", "(T) view =" + (T) view);
		return (T) view;
	}

	public View getConvertView() {
		
		return mConvertView;
	}
	
	/**
	 * 設置TextView的值
	 * @param viewId
	 * @param text
	 * @return
	 * 
	 * 可以根據項目item需要控件的需求添加設置各種控件的方法
	 */
	public ViewHolder setText(int viewId, String text){
		TextView tv = getView(viewId);
		tv.setText(text);
		return this;
		
	}
	
	/**
	 * 設置本地圖片資源
	 * @param viewId
	 * @param resId
	 * @return
	 */
	public ViewHolder setImageResource(int viewId, int resId){
		ImageView iv = getView(viewId);
		iv.setImageResource(resId);
		return this;
		
	}
	/**
	 * 設置CheckBox的選擇狀態
	 * @param viewId
	 * @param resId
	 * @return
	 */
	public ViewHolder setCheckd(int viewId, Boolean b){
		CheckBox cb = getView(viewId);
		cb.setChecked(b);
		return this;
		
	}
}

寫的item佈局

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="3dp"
    >
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="adsfsdfsdfsdfsdfsdf"
        android:background="@drawable/rec"
        android:padding="5dp"
        />
</android.support.constraint.ConstraintLayout>

 

測試佈局

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.roy.widgetdemo.CustomLayout
        android:id="@+id/custom"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:horizontal_spacing="10dp"
        >

    </com.roy.widgetdemo.CustomLayout>

</android.support.constraint.ConstraintLayout>

來測試以下代碼

package com.roy.widgetdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CustomLayout customLayout =  findViewById(R.id.custom);

        List<String>  list = new ArrayList<>();
        for (int i = 0; i <5; i++) {
            list.add("你好啊美女");
            list.add("美女有空嗎約嗎");
            list.add("你幾個啊");
            list.add("不想");
            list.add("你爲啥呢");
        }

        CommonAdapter<String>   commonAdapter = new CommonAdapter<String>(this,list,R.layout.item) {
            @Override
            public void convert(ViewHolder holder, String s, int pos) {
             TextView tv = holder.getView(R.id.tv);
              tv.setText(s);
            }
        };
        customLayout.setAdapter(commonAdapter);

    }
}

效果:

 

demo:下載地址

https://download.csdn.net/download/cly19940419/11964701

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章