我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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();
      
      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;

public interface GrideTagBaseAdapter {
  List<View> getViews();
}

抽象适配器AbsGridTagsAdapter


package humoursz.gridtag.test.adapter;
import android.database.DataSetObservable;
import android.database.DataSetObserver;

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;

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;

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));
    
    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);
    }
  }
  
  private int getChildRealWidthSize(View child) {
    LayoutParams lp = getChildLayoutParams(child);
    int size = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
    return size;
  }
  
  private int getChildRealHeightSize(View child) {
    LayoutParams lp = getChildLayoutParams(child);
    int size = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
    return size;
  }
  
  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开发中的大家。

您可能感兴趣的文章:从源码解析Android中View的容器ViewGroupAndroid应用开发中自定义ViewGroup视图容器的教程Android自定义ViewGroup实现标签流容器FlowLayoutAndroid自定义控件之继承ViewGroup创建新容器Android中实现多行、水平滚动的分页的Gridview实例源码android listview 水平滚动和垂直滚动的小例子Android使用RecyclerView实现水平滚动控件Android实现Activity水平和垂直滚动条的方法详解Android使GridView横向水平滚动的实现方式Android使用Recyclerview实现图片水平自动循环滚动效果Android开发实现自定义水平滚动的容器示例


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

前言 在一些APP中我们可以看到一些存放标签的容器控件,和我们平时使用的一些布局方式有些不同,它们一般都可以自动适应屏幕的宽度进行布局,根据对自定义控件的一些理解,今天写一个简单的标签容器控件,给大家参考学习。 下面这个是我在手机上截取的一
2022-06-06

Android ToolBar控件详解及实例

ToolBar控件详解 在Activity中添加ToolBar1.添加库dependencies {...compile "com.android.support:appcompat-v7:18.0.+" }2.Activity要继承App
2022-06-06

Android Tab 控件详解及实例

Android Tab 控件详解及实例 在桌面应用中Tab控件使用得非常普遍,那么我们经常在Android中也见到以Tab进行布局的客户端。那么Android中的Tab是如何使用的呢? 1.Activitypackage com.wicre
2022-06-06

Android控件拖动实例详解

Android控件拖动 Android控件的拖动,主要是通过设置控件的setOnTouchListener()方法,重写它的onTouch()方法。然后通过MotionEvent的不同事件,进行判断,主要是在MotionEvent.ACTI
2022-06-06

Android控件之ListView用法实例详解

本文实例讲述了Android控件之ListView用法。分享给大家供大家参考。具体如下: 示例一: 在android开发中ListView是比较常用的组件,它以列表的形式展示具体内容,并且能够根据数据的长度自适应显示。 main.xml布局
2022-06-06

Android自定义控件样式实例详解

本文实例讲述了Android自定义控件样式的方法。分享给大家供大家参考,具体如下: Android控件样式自定义是用定义在drawable文件夹下的XML文件实现,在布局文件中通过设置控件的background属性达到效果。 一、控件常见状
2022-06-06

Android自定义日历控件实例详解

为什么要自定义控件 有时,原生控件不能满足我们对于外观和功能的需求,这时候可以自定义控件来定制外观或功能;有时,原生控件可以通过复杂的编码实现想要的功能,这时候可以自定义控件来提高代码的可复用性。 如何自定义控件 下面我通过我在github
2022-06-06

详细解读Android系统中的application标签

< application /> :应用的声明。 这个元素包含了子元素,这些子元素声明了应用的组件,元素的属性将会影响应用下的所有组件。很多属性为组件设置了默认值,有些属性设置了全局值并且不能被组件修改。 的子节
2022-06-06

kubernetes中控制器和标签的示例分析

小编给大家分享一下kubernetes中控制器和标签的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!01 k8s中的常用控制器 之前我们了解了Pod是k8s集群中调度的最小单元,Pod是由Pause容器+
2023-06-14

Android开发之TimePicker控件用法实例详解

本文实例分析了Android开发之TimePicker控件用法。分享给大家供大家参考,具体如下: 新建项目: New Android Project-> Project name:HelloSpinner Build Target:Andr
2022-06-06

Android 自定义控件详解及实例代码

开发自定义控件的步骤:1、了解View的工作原理 2、 编写继承自View的子类 3、 为自定义View类增加属性 4、 绘制控件 5、 响应用户消息 6 、自定义回调函数 一、View结构原理Android系统的视图结构的设计也
2022-06-06

Android 控制ScrollView滚动的实例详解

Android 控制ScrollView滚动的实例详解在开发中,我们经常需要更新列表,并将列表拉倒最底部,比如发表微博,聊天界面等等,这里有两种办法,第一种,使用scrollTo():public static void scrollToB
2023-05-30

Android ListView里控件添加监听方法的实例详解

Android ListView里控件添加监听方法的实例详解 关于ListView,算是android中比较常见的控件,在ListView我们通常需要一个模板,这个模板指的不是住模块,而是配置显示在ListView里面的东西,今天做项目的时
2023-05-30

获取Android签名MD5的方式实例详解

平时开发,很多第三方需要配置应用签名,比如百度,高德地图等,下面这篇文章主要给大家介绍了关于获取Android签名MD5的方式,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
2023-02-09

Android 自定义标题栏的实例详解

Android 自定义标题栏的实例详解开发 Android APP 经常会用到自定义标题栏,而有多级页面的情况下还需要给自定义标题栏传递数据。本文要点:自定义标题填充不完整自定义标题栏返回按钮的点击事件一、代码这里先介绍一下流程: 1.
2023-05-30

Android 中读取Excel文件实例详解

Android 中读取Excel文件实例详解 最近有个需求需要在app内置数据,新来的产品扔给了我两个Excel表格就不管了(两个表格格式还不统一。。。),于是通过度娘等方法找到了Android中读取Excel表格文件的一种方法,记录一下。
2022-06-06

Android自定义控件eBook实现翻书效果实例详解

本文实例讲述了Android自定义控件eBook实现翻书效果的方法。分享给大家供大家参考,具体如下: 效果图:Book.java文件:package com.book; import android.app.Activity; import
2022-06-06

Android Chronometer控件实现计时器函数详解

本文为大家演示了如何使用Chronometer控件实现Android计时器的实例。 先贴上最终的实现效果图:Android计时器实现思路 使用Chronometer控件实现计器的操作。通过设置setBase(long base)来设置初始时
2022-06-06

Android中DownloadManager实现文件下载实例详解

Android中DownloadManager实现文件下载 下载 创建下载链接DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); 设置
2022-06-06

举例讲解Android中ViewPager中的PagerTitleStrip子控件

先看个简单的,先上个效果图,吸引大家一下眼球。三个页面间的滑动,此时是带着上面的标题一块滑动的。 看一下android 对于PagerTitleStrip的官方解释:PagerTitleStrip是ViewPager的一个关于当前页面、上一
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录