国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android中標簽容器控件的實例詳解

2019-12-12 05:54:53
字體:
來源:轉載
供稿:網友

前言

在一些APP中我們可以看到一些存放標簽的容器控件,和我們平時使用的一些布局方式有些不同,它們一般都可以自動適應屏幕的寬度進行布局,根據對自定義控件的一些理解,今天寫一個簡單的標簽容器控件,給大家參考學習。

下面這個是我在手機上截取的一個實例,是在MIUI8系統上截取的

這個是我實現的效果圖

原理介紹

根據對整個控件的效果分析,大致可以將控件分別從以下這幾個角度進行分析:

1.首先涉及到自定義的ViewGroup,因為現有的控件沒法滿足我們的布局效果,就涉及到要重寫onMeasure和onLayout,這里需要注意的問題是自定義View的時候,我們需要考慮到View的Padding屬性,而在自定義ViewGroup中我們需要在onLayout中考慮Child控件的margin屬性否則子類設置這個屬性將會失效。整個View的繪制流程是這樣的:

最頂層的ViewRoot執行performTraversals然后分別開始對各個View進行層級的測量、布局、繪制,整個流程是一層一層進行的,也就是說父視圖測量時會調用子視圖的測量方法,子視圖調孫視圖方法,一直測量到葉子節點,performTraversals這個函數翻譯過來很直白,執行遍歷,就說明了這種層級關系。

2.該控件形式上和ListView的形式比較相近,所以在這里我也模仿ListView的Adapter模式實現了對控件內容的操作,這里對ListView的setAdapter和Adapter的notifyDataSetChanged方法做個簡單的解釋:

在ListView調用setAdapter后,ListView會去注冊一個Observer對象到這個adapter上,然后當我們在改變設置到adapter上的數據發改變時,我們會調用adapter的notifyDataSetChanged方法,這個方法就會通知所有監聽了該Adapter數據改變時的Observer對象,這就是典型的監聽者模式,這時由于ListView中的內部成員對象監聽了該事件,就可以知道數據源發生了改變,我們需要對真個控件重新進行繪制了,下面來一些相關的源碼。

Adapter的notifyDataSetChanged

public void notifyDataSetChanged() {    mDataSetObservable.notifyChanged();  }

ListView的setAdapter方法

@Override  public void setAdapter(ListAdapter adapter) {    /**     *每次設置新的適配的時候,如果現在有的話會做一個解除監聽的操作     */    if (mAdapter != null && mDataSetObserver != null) {      mAdapter.unregisterDataSetObserver(mDataSetObserver);    }    resetList();    mRecycler.clear();    /** 省略部分代碼.....  */    if (mAdapter != null) {      mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();      mOldItemCount = mItemCount;      mItemCount = mAdapter.getCount();      checkFocus();      /**      *在這里對adapter設置了監聽,      *使用的是AdapterDataSetObserver類的對象,該對象定義在ListView的父類AdapterView中      */      mDataSetObserver = new AdapterDataSetObserver();      mAdapter.registerDataSetObserver(mDataSetObserver);      /** 省略 */    } else {      /** 省略 */    }    requestLayout();  }

AdapterView中的內部類AdapterDataSetObserver

class AdapterDataSetObserver extends DataSetObserver {    private Parcelable mInstanceState = null;    @Override    public void onChanged() {      /* ***代碼略*** */      checkFocus();      requestLayout();    }    @Override    public void onInvalidated() {      /* ***代碼略*** */      checkFocus();      requestLayout();    }    public void clearSavedState() {      mInstanceState = null;    }  }

一段偽代碼表示

ListView{  Observer observer{     onChange(){       change;     }  }  setAdapter(Adapter adapter){     adapter.register(observer);  }}Adapter{  List<Observer> mObservable;  register(observer){    mObservable.add(observer);  }  notifyDataSetChanged(){    for(i-->mObserverable.size()){      mObserverable.get(i).onChange    }  }}

實現過程

獲取ViewItem的接口

package humoursz.gridtag.test.adapter;import android.view.View;import java.util.List;/** * Created by zhangzhiquan on 2016/7/19. */public interface GrideTagBaseAdapter {  List<View> getViews();}

抽象適配器AbsGridTagsAdapter

package humoursz.gridtag.test.adapter;import android.database.DataSetObservable;import android.database.DataSetObserver;/** * Created by zhangzhiquan on 2016/7/19. */public abstract class AbsGridTagsAdapter implements GrideTagBaseAdapter {  DataSetObservable mObservable = new DataSetObservable();  public void notification(){    mObservable.notifyChanged();  }  public void registerObserve(DataSetObserver observer){    mObservable.registerObserver(observer);  }  public void unregisterObserve(DataSetObserver observer){    mObservable.unregisterObserver(observer);  }}

此效果中的需要的適配器,實現了getView接口,主要是模仿了ListView的BaseAdapter

package humoursz.gridtag.test.adapter;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.widget.TextView;import java.util.ArrayList;import java.util.List;import humoursz.gridtag.test.R;import humoursz.gridtag.test.util.UIUtil;import humoursz.gridtag.test.widget.GridTagView;/** * Created by zhangzhiquan on 2016/7/19. */public class MyGridTagAdapter extends AbsGridTagsAdapter {  private Context mContext;  private List<String> mTags;  public MyGridTagAdapter(Context context, List<String> tags) {    mContext = context;    mTags = tags;  }  @Override  public List<View> getViews() {    List<View> list = new ArrayList<>();    for (int i = 0; i < mTags.size(); i++) {      TextView tv = (TextView) LayoutInflater.from(mContext)          .inflate(R.layout.grid_tag_item_text, null);      tv.setText(mTags.get(i));      GridTagView.LayoutParams lp = new GridTagView          .LayoutParams(GridTagView.LayoutParams.WRAP_CONTENT          ,GridTagView.LayoutParams.WRAP_CONTENT);      lp.margin(UIUtil.dp2px(mContext, 5));      tv.setLayoutParams(lp);      list.add(tv);    }    return list;  }}

最后是主角GridTagsView控件

package humoursz.gridtag.test.widget;import android.content.Context;import android.database.DataSetObserver;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.view.ViewGroup;import java.util.List;import humoursz.gridtag.test.adapter.AbsGridTagsAdapter;/** * Created by zhangzhiquan on 2016/7/18. */public class GridTagView extends ViewGroup {  private int mLines = 1;  private int mWidthSize = 0;  private AbsGridTagsAdapter mAdapter;  private GTObserver mObserver = new GTObserver();  public GridTagView(Context context) {    this(context, null);  }  public GridTagView(Context context, AttributeSet attrs) {    this(context, attrs, 0);  }  public GridTagView(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);  }  public void setAdapter(AbsGridTagsAdapter adapter) {    if (mAdapter != null) {      mAdapter.unregisterObserve(mObserver);    }    mAdapter = adapter;    mAdapter.registerObserve(mObserver);    mAdapter.notification();  }  @Override  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    int widthSize = MeasureSpec.getSize(widthMeasureSpec);    int heightSize = MeasureSpec.getSize(heightMeasureSpec);    int curWidthSize = 0;    int childHeight = 0;    mLines = 1;    for (int i = 0; i < getChildCount(); ++i) {      View child = getChildAt(i);      measureChild(child, widthMeasureSpec, heightMeasureSpec);      curWidthSize += getChildRealWidthSize(child);      if (curWidthSize > widthSize) {        /**         * 計算一共需要多少行,用于計算控件的高度         * 計算方法是,如果當前控件放下后寬度超過         * 容器本身的高度,就放到下一行         */        curWidthSize = getChildRealWidthSize(child);        mLines++;      }      if (childHeight == 0) {        /**         * 在第一次計算時拿到字視圖的高度作為計算基礎         */        childHeight = getChildRealHeightSize(child);      }    }    mWidthSize = widthSize;    setMeasuredDimension(widthSize, childHeight == 0 ? heightSize : childHeight * mLines);  }  @Override  protected void onLayout(boolean changed, int l, int t, int r, int b) {    if (getChildCount() == 0)      return;    int childCount = getChildCount();    LayoutParams lp = getChildLayoutParams(getChildAt(0));    /**     * 初始的左邊界在自身的padding left和child的margin后     * 初始的上邊界原理相同     */    int left = getPaddingLeft() + lp.leftMargin;    int top = getPaddingTop() + lp.topMargin;    int curLeft = left;    for (int i = 0; i < childCount; ++i) {      View child = getChildAt(i);      int right = curLeft + getChildRealWidthSize(child);      /**       * 計算如果放下當前試圖后整個一行到右側的距離       * 如果超過控件寬那就放到下一行,并且左邊距還原,上邊距等于下一行的開始       */      if (right > mWidthSize) {        top += getChildRealHeightSize(child);        curLeft = left;      }      child.layout(curLeft, top, curLeft + child.getMeasuredWidth(), top + child.getMeasuredHeight());      /**       * 下一個控件的左邊開始距離是上一個控件的右邊       */      curLeft += getChildRealWidthSize(child);    }  }  /**   * 獲取childView實際占用寬度   * @param child   * @return 控件實際占用的寬度,需要算上margin否則margin不生效   */  private int getChildRealWidthSize(View child) {    LayoutParams lp = getChildLayoutParams(child);    int size = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;    return size;  }  /**   * 獲取childView實際占用高度   * @param child   * @return 實際占用高度需要考慮上下margin   */  private int getChildRealHeightSize(View child) {    LayoutParams lp = getChildLayoutParams(child);    int size = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;    return size;  }  /**   * 獲取LayoutParams屬性   * @param child   * @return   */  private LayoutParams getChildLayoutParams(View child) {    LayoutParams lp;    if (child.getLayoutParams() instanceof LayoutParams) {      lp = (LayoutParams) child.getLayoutParams();    } else {      lp = (LayoutParams) generateLayoutParams(child.getLayoutParams());    }    return lp;  }  @Override  public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attr) {    return new LayoutParams(getContext(), attr);  }  @Override  protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {    return new LayoutParams(p);  }  public static class LayoutParams extends MarginLayoutParams {    public LayoutParams(Context c, AttributeSet attrs) {      super(c, attrs);    }    public LayoutParams(int width, int height) {      super(width, height);    }    public LayoutParams(MarginLayoutParams source) {      super(source);    }    public LayoutParams(ViewGroup.LayoutParams source) {      super(source);    }    public void marginLeft(int left) {      this.leftMargin = left;    }    public void marginRight(int r) {      this.rightMargin = r;    }    public void marginTop(int t) {      this.topMargin = t;    }    public void marginBottom(int b) {      this.bottomMargin = b;    }    public void margin(int m){      this.leftMargin = m;      this.rightMargin = m;      this.topMargin = m;      this.bottomMargin = m;    }  }  private class GTObserver extends DataSetObserver {    @Override    public void onChanged() {      removeAllViews();      List<View> list = mAdapter.getViews();      for (int i = 0; i < list.size(); i++) {        addView(list.get(i));      }    }    @Override    public void onInvalidated() {      Log.d("Mrz","fd");    }  }}

MainActivity

package humoursz.gridtag.test;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import java.util.List;import humoursz.gridtag.test.adapter.MyGridTagAdapter;import humoursz.gridtag.test.util.ListUtil;import humoursz.gridtag.test.widget.GridTagView;public class MainActivity extends AppCompatActivity {  MyGridTagAdapter adapter;  GridTagView mGridTag;  List<String> mList;  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    mGridTag = (GridTagView)findViewById(R.id.grid_tags);    mList = ListUtil.getGridTagsList(20);    adapter = new MyGridTagAdapter(this,mList);    mGridTag.setAdapter(adapter);  }  public void onClick(View v){    mList.removeAll(mList);    mList.addAll(ListUtil.getGridTagsList(20));    adapter.notification();  }}

XML 文件

<?xml version="1.0" encoding="utf-8"?><RelativeLayout 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"  tools:context="humoursz.gridtag.test.MainActivity">  <humoursz.gridtag.test.widget.GridTagView    android:id="@+id/grid_tags"    android:layout_width="match_parent"    android:layout_height="wrap_content">  </humoursz.gridtag.test.widget.GridTagView>  <Button    android:layout_centerInParent="true"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:onClick="onClick"    android:text="換一批"/></RelativeLayout>

以上就是Android中標簽容器控件的全部實現過程,這樣一個簡單的控件就寫好了,主要需要注意measurelayout否則很多效果都會失效,安卓中的LinearLayout之類的控件實際實現起來要復雜的很多,因為支持的屬性實在的太多了,多動手實踐可以幫助理解,希望本文能幫助到在Android開發中的大家。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 昌邑市| 绥芬河市| 安顺市| 颍上县| 新余市| 百色市| 遵义县| 塘沽区| 惠水县| 鲜城| 贵港市| 萨迦县| 额尔古纳市| 报价| 织金县| 阳城县| 中西区| 西充县| 蚌埠市| 华容县| 泉州市| 牡丹江市| 西宁市| 金寨县| 万载县| 达拉特旗| 德庆县| 进贤县| 罗江县| 定陶县| 东至县| 容城县| 汨罗市| 察雅县| 偃师市| 光山县| 新野县| 泾川县| 汨罗市| 澎湖县| 阿拉尔市|