当前位置: 移动技术网 > IT编程>移动开发>Android > Android中标签容器控件的实例详解

Android中标签容器控件的实例详解

2019年07月24日  | 移动技术网IT编程  | 我要评论

高官的暗夜新娘,计算机试题及答案,薰香不怕贾公知

前言

在一些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开发中的大家。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网