热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

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

在Android开发过程中,常常会遇到这样的场景:我们展示一种物品或者为某一事物添加一些标签。比如说,我们买一件衣服,可以有以下几种标签:杰克琼斯,男士,运动等等。本文将实例介绍Android中标签容器控件的实现过程。

前言

在一些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 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 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 mTags;

  public MyGridTagAdapter(Context context, List tags) {
    mCOntext= context;
    mTags = tags;
  }

  @Override
  public List getViews() {
    List list = new ArrayList<>();
    for (int i = 0; i 

最后是主角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  widthSize) {
        /**
         * 计算一共需要多少行,用于计算控件的高度
         * 计算方法是,如果当前控件放下后宽度超过
         * 容器本身的高度,就放到下一行
         */
        curWidthSize = getChildRealWidthSize(child);
        mLines++;
      }
      if (childHeight == 0) {
        /**
         * 在第一次计算时拿到字视图的高度作为计算基础
         */
        childHeight = getChildRealHeightSize(child);
      }
    }
    mWidthSize = widthSize;
    setMeasuredDimension(widthSize, childHeight == 0 &#63; 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  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 list = mAdapter.getViews();
      for (int i = 0; i 

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 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 文件

<&#63;xml version="1.0" encoding="utf-8"&#63;>


  
  
  

以上就是Android中标签容器控件的全部实现过程,这样一个简单的控件就写好了,主要需要注意measurelayout否则很多效果都会失效,安卓中的LinearLayout之类的控件实际实现起来要复杂的很多,因为支持的属性实在的太多了,多动手实践可以帮助理解,希望本文能帮助到在Android开发中的大家。


推荐阅读
  • Android 九宫格布局详解及实现:人人网应用示例
    本文深入探讨了人人网Android应用中独特的九宫格布局设计,解析其背后的GridView实现原理,并提供详细的代码示例。这种布局方式不仅美观大方,而且在现代Android应用中较为少见,值得开发者借鉴。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了如何使用Spring Boot进行高效开发,涵盖了配置、实例化容器以及核心注解的使用方法。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 本文介绍如何在 Unity 的 XML 配置文件中,将参数传递给自定义生命周期管理器的构造函数。我们将详细探讨 CustomLifetimeManager 类的实现及其配置方法。 ... [详细]
  • 解决JAX-WS动态客户端工厂弃用问题并迁移到XFire
    在处理Java项目中的JAR包冲突时,我们遇到了JaxWsDynamicClientFactory被弃用的问题,并成功将其迁移到org.codehaus.xfire.client。本文详细介绍了这一过程及解决方案。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 本文介绍如何使用布局文件在Android应用中排列多行TextView和Button,使其占据屏幕的特定比例,并提供示例代码以帮助理解和实现。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文探讨了在 ASP.NET MVC 5 中实现松耦合组件的方法。通过分离关注点,应用程序的各个组件可以更加独立且易于维护和测试。文中详细介绍了依赖项注入(DI)及其在实现松耦合中的作用。 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
author-avatar
哇哈时候_206
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有