Android多级树形列表控件

我们开发app过程中,经常会碰到需要 多级列表展示的效果。而Android原生sdk中根本没有3级 4级甚至更多级别的列表控件。
所以我们就要自己去实现一个类似treeListView 的控件,下面这个是我项目中的一个效果图,可支持多级列表扩展。

android中有ExpandListView控件,但是这个控件只支持两级列表。对于多级列表如果重写这个不是很好用。
实现这种列表 思想就是递归,构造一个子父级的关系。
话不多说 代码中体会
Activity

package com.example.customtreeviewdemo; 

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

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast; 

import com.example.customtreeviewdemo.bean.MyNodeBean;
import com.example.customtreeviewdemo.tree.Node;
import com.example.customtreeviewdemo.tree.TreeListViewAdapter.OnTreeNodeClickListener; 

public class MainActivity extends Activity { 

  private ListView treeLv;
  private Button checkSwitchBtn;
  private MyTreeListViewAdapter<MyNodeBean> adapter;
  private List<MyNodeBean> mDatas = new ArrayList<MyNodeBean>();
  //标记是显示Checkbox还是隐藏
  private boolean isHide = true; 

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); 

    initDatas();
    treeLv = (ListView) this.findViewById(R.id.tree_lv);
    checkSwitchBtn = (Button)this.findViewById(R.id.check_switch_btn); 

    checkSwitchBtn.setOnClickListener(new OnClickListener(){ 

      @Override
      public void onClick(View v) {
        if(isHide){
          isHide = false;
        }else{
          isHide = true;
        } 

        adapter.updateView(isHide);
      } 

    });
    try {
      adapter = new MyTreeListViewAdapter<MyNodeBean>(treeLv, this,
          mDatas, 10, isHide); 

      adapter.setOnTreeNodeClickListener(new OnTreeNodeClickListener() {
        @Override
        public void onClick(Node node, int position) {
          if (node.isLeaf()) {
            Toast.makeText(getApplicationContext(), node.getName(),
                Toast.LENGTH_SHORT).show();
          }
        } 

        @Override
        public void onCheckChange(Node node, int position,
            List<Node> checkedNodes) { 

          StringBuffer sb = new StringBuffer();
          for (Node n : checkedNodes) {
            int pos = n.getId() - 1;
            sb.append(mDatas.get(pos).getName()).append("---")
                .append(pos + 1).append(";"); 

          } 

          Toast.makeText(getApplicationContext(), sb.toString(),
              Toast.LENGTH_SHORT).show();
        } 

      });
    } catch (IllegalArgumentException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }
    treeLv.setAdapter(adapter); 

  } 

  private void initDatas() {
    mDatas.add(new MyNodeBean(1, 0, "中国古代"));
    mDatas.add(new MyNodeBean(2, 1, "唐朝"));
    mDatas.add(new MyNodeBean(3, 1, "宋朝"));
    mDatas.add(new MyNodeBean(4, 1, "明朝"));
    mDatas.add(new MyNodeBean(5, 2, "李世民"));
    mDatas.add(new MyNodeBean(6, 2, "李白")); 

    mDatas.add(new MyNodeBean(7, 3, "赵匡胤"));
    mDatas.add(new MyNodeBean(8, 3, "苏轼")); 

    mDatas.add(new MyNodeBean(9, 4, "朱元璋"));
    mDatas.add(new MyNodeBean(10, 4, "唐伯虎"));
    mDatas.add(new MyNodeBean(11, 4, "文征明"));
    mDatas.add(new MyNodeBean(12, 7, "赵建立"));
    mDatas.add(new MyNodeBean(13, 8, "苏东东"));
    mDatas.add(new MyNodeBean(14, 10, "秋香"));
  }
}

Adapter
这个adapter是继承了自己的定义的一个TreeListViewAdapter,核心实现都是在TreeListViewAdapter这个里面

package com.example.customtreeviewdemo; 

import java.util.List; 

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView; 

import com.example.customtreeviewdemo.tree.Node;
import com.example.customtreeviewdemo.tree.TreeListViewAdapter; 

public class MyTreeListViewAdapter<T> extends TreeListViewAdapter<T> { 

  public MyTreeListViewAdapter(ListView mTree, Context context,
      List<T> datas, int defaultExpandLevel,boolean isHide)
      throws IllegalArgumentException, IllegalAccessException {
    super(mTree, context, datas, defaultExpandLevel,isHide);
  } 

  @SuppressWarnings("unchecked")
  @Override
  public View getConvertView(Node node, int position, View convertView,
      ViewGroup parent) {
    ViewHolder viewHolder = null;
    if (convertView == null)
    {
      convertView = mInflater.inflate(R.layout.list_item, parent, false);
      viewHolder = new ViewHolder();
      viewHolder.icon = (ImageView) convertView
          .findViewById(R.id.id_treenode_icon);
      viewHolder.label = (TextView) convertView
          .findViewById(R.id.id_treenode_name);
      viewHolder.checkBox = (CheckBox)convertView.findViewById(R.id.id_treeNode_check); 

      convertView.setTag(viewHolder); 

    } else
    {
      viewHolder = (ViewHolder) convertView.getTag();
    } 

    if (node.getIcon() == -1)
    {
      viewHolder.icon.setVisibility(View.INVISIBLE);
    } else
    {
      viewHolder.icon.setVisibility(View.VISIBLE);
      viewHolder.icon.setImageResource(node.getIcon());
    } 

    if(node.isHideChecked()){
      viewHolder.checkBox.setVisibility(View.GONE);
    }else{
      viewHolder.checkBox.setVisibility(View.VISIBLE);
      setCheckBoxBg(viewHolder.checkBox,node.isChecked());
    }
    viewHolder.label.setText(node.getName()); 

    return convertView;
  }
  private final class ViewHolder
  {
    ImageView icon;
    TextView label;
    CheckBox checkBox;
  } 

  /**
   * checkbox是否显示
   * @param cb
   * @param isChecked
   */
  private void setCheckBoxBg(CheckBox cb,boolean isChecked){
    if(isChecked){
      cb.setBackgroundResource(R.drawable.check_box_bg_check);
    }else{
      cb.setBackgroundResource(R.drawable.check_box_bg);
    }
  }
}

自定义TreeListViewAdapter  这个是整个树形结构的一个适配器,这里面主要是实现对Node节点的操作 点击,选中改变 更新等

package com.example.customtreeviewdemo.tree; 

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

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.RelativeLayout; 

/**
 * tree适配器
 * @param <T>
 */
public abstract class TreeListViewAdapter<T> extends BaseAdapter { 

  protected Context mContext;
  /**
   * 存储所有可见的Node
   */
  protected List<Node> mNodes;
  protected LayoutInflater mInflater;
  /**
   * 存储所有的Node
   */
  protected List<Node> mAllNodes; 

  /**
   * 点击的回调接口
   */
  private OnTreeNodeClickListener onTreeNodeClickListener; 

  public interface OnTreeNodeClickListener {
    /**
     * 处理node click事件
     * @param node
     * @param position
     */
    void onClick(Node node, int position);
    /**
     * 处理checkbox选择改变事件
     * @param node
     * @param position
     * @param checkedNodes
     */
    void onCheckChange(Node node, int position,List<Node> checkedNodes);
  } 

  public void setOnTreeNodeClickListener(
      OnTreeNodeClickListener onTreeNodeClickListener) {
    this.onTreeNodeClickListener = onTreeNodeClickListener;
  } 

  /**
   *
   * @param mTree
   * @param context
   * @param datas
   * @param defaultExpandLevel
   *      默认展开几级树
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   */
  public TreeListViewAdapter(ListView mTree, Context context, List<T> datas,
      int defaultExpandLevel, boolean isHide)
      throws IllegalArgumentException, IllegalAccessException {
    mContext = context;
    /**
     * 对所有的Node进行排序
     */
    mAllNodes = TreeHelper
        .getSortedNodes(datas, defaultExpandLevel, isHide);
    /**
     * 过滤出可见的Node
     */
    mNodes = TreeHelper.filterVisibleNode(mAllNodes);
    mInflater = LayoutInflater.from(context); 

    /**
     * 设置节点点击时,可以展开以及关闭;并且将ItemClick事件继续往外公布
     */
    mTree.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view,
          int position, long id) {
        expandOrCollapse(position); 

        if (onTreeNodeClickListener != null) {
          onTreeNodeClickListener.onClick(mNodes.get(position),
              position);
        }
      } 

    }); 

  } 

  /**
   * 相应ListView的点击事件 展开或关闭某节点
   *
   * @param position
   */
  public void expandOrCollapse(int position) {
    Node n = mNodes.get(position); 

    if (n != null)// 排除传入参数错误异常
    {
      if (!n.isLeaf()) {
        n.setExpand(!n.isExpand());
        mNodes = TreeHelper.filterVisibleNode(mAllNodes);
        notifyDataSetChanged();// 刷新视图
      }
    }
  } 

  @Override
  public int getCount() {
    return mNodes.size();
  } 

  @Override
  public Object getItem(int position) {
    return mNodes.get(position);
  } 

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

  @Override
  public View getView(final int position, View convertView, ViewGroup parent) {
    final Node node = mNodes.get(position); 

    convertView = getConvertView(node, position, convertView, parent);
    // 设置内边距
    convertView.setPadding(node.getLevel() * 30, 3, 3, 3);
    if (!node.isHideChecked()) {
      //获取各个节点所在的父布局
      RelativeLayout myView = (RelativeLayout) convertView;
      //父布局下的CheckBox
      CheckBox cb = (CheckBox) myView.getChildAt(1);
      cb.setOnCheckedChangeListener(new OnCheckedChangeListener(){ 

        @Override
        public void onCheckedChanged(CompoundButton buttonView,
            boolean isChecked) {
          TreeHelper.setNodeChecked(node, isChecked);
          List<Node> checkedNodes = new ArrayList<Node>();
          for(Node n:mAllNodes){
            if(n.isChecked()){
              checkedNodes.add(n);
            }
          } 

          onTreeNodeClickListener.onCheckChange(node,position,checkedNodes);
          TreeListViewAdapter.this.notifyDataSetChanged();
        } 

      });
    } 

    return convertView;
  } 

  public abstract View getConvertView(Node node, int position,
      View convertView, ViewGroup parent); 

  /**
   * 更新
   * @param isHide
   */
  public void updateView(boolean isHide){
    for(Node node:mAllNodes){
      node.setHideChecked(isHide);
    } 

    this.notifyDataSetChanged();
  } 

}

node 模型类

package com.example.customtreeviewdemo.bean; 

public class MyNodeBean {
  /**
   * 节点Id
   */
  private int id;
  /**
   * 节点父id
   */
  private int pId;
  /**
   * 节点name
   */
  private String name;
  /**
   *
   */
  private String desc;
  /**
   * 节点名字长度
   */
  private long length; 

  public MyNodeBean(int id, int pId, String name) {
    super();
    this.id = id;
    this.pId = pId;
    this.name = name;
  } 

  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public int getPid() {
    return pId;
  }
  public void setPid(int pId) {
    this.pId = pId;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getDesc() {
    return desc;
  }
  public void setDesc(String desc) {
    this.desc = desc;
  }
  public long getLength() {
    return length;
  }
  public void setLength(long length) {
    this.length = length;
  } 

}

TreeHelper这个也是核心操作类,主要功能是将业务数据和节点数据进行匹配

package com.example.customtreeviewdemo.tree; 

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List; 

import com.example.customtreeviewdemo.R; 

public class TreeHelper { 

  /**
   * 根据所有节点获取可见节点
   *
   * @param allNodes
   * @return
   */
  public static List<Node> filterVisibleNode(List<Node> allNodes) {
    List<Node> visibleNodes = new ArrayList<Node>();
    for (Node node : allNodes) {
      // 如果为根节点,或者上层目录为展开状态
      if (node.isRoot() || node.isParentExpand()) {
        setNodeIcon(node);
        visibleNodes.add(node);
      }
    }
    return visibleNodes;
  } 

  /**
   * 获取排序的所有节点
   *
   * @param datas
   * @param defaultExpandLevel
   * @return
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   */
  public static <T> List<Node> getSortedNodes(List<T> datas,
      int defaultExpandLevel, boolean isHide)
      throws IllegalAccessException, IllegalArgumentException {
    List<Node> sortedNodes = new ArrayList<Node>();
    // 将用户数据转化为List<Node>
    List<Node> nodes = convertData2Nodes(datas, isHide);
    // 拿到根节点
    List<Node> rootNodes = getRootNodes(nodes);
    // 排序以及设置Node间关系
    for (Node node : rootNodes) {
      addNode(sortedNodes, node, defaultExpandLevel, 1);
    }
    return sortedNodes;
  } 

  /**
   * 把一个节点上的所有的内容都挂上去
   */
  private static void addNode(List<Node> nodes, Node node,
      int defaultExpandLeval, int currentLevel) { 

    nodes.add(node);
    if (defaultExpandLeval >= currentLevel) {
      node.setExpand(true);
    } 

    if (node.isLeaf())
      return;
    for (int i = 0; i < node.getChildrenNodes().size(); i++) {
      addNode(nodes, node.getChildrenNodes().get(i), defaultExpandLeval,
          currentLevel + 1);
    }
  } 

  /**
   * 获取所有的根节点
   *
   * @param nodes
   * @return
   */
  public static List<Node> getRootNodes(List<Node> nodes) {
    List<Node> rootNodes = new ArrayList<Node>();
    for (Node node : nodes) {
      if (node.isRoot()) {
        rootNodes.add(node);
      }
    } 

    return rootNodes;
  } 

  /**
   * 将泛型datas转换为node
   *
   * @param datas
   * @return
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   */
  public static <T> List<Node> convertData2Nodes(List<T> datas, boolean isHide)
      throws IllegalAccessException, IllegalArgumentException {
    List<Node> nodes = new ArrayList<Node>();
    Node node = null; 

    for (T t : datas) {
      int id = -1;
      int pId = -1;
      String name = null; 

      Class<? extends Object> clazz = t.getClass();
      Field[] declaredFields = clazz.getDeclaredFields();
      /**
       * 与MyNodeBean实体一一对应
       */
      for (Field f : declaredFields) {
        if ("id".equals(f.getName())) {
          f.setAccessible(true);
          id = f.getInt(t);
        } 

        if ("pId".equals(f.getName())) {
          f.setAccessible(true);
          pId = f.getInt(t);
        } 

        if ("name".equals(f.getName())) {
          f.setAccessible(true);
          name = (String) f.get(t);
        } 

        if ("desc".equals(f.getName())) {
          continue;
        } 

        if ("length".equals(f.getName())) {
          continue;
        } 

        if (id == -1 && pId == -1 && name == null) {
          break;
        }
      } 

      node = new Node(id, pId, name);
      node.setHideChecked(isHide);
      nodes.add(node);
    } 

    /**
     * 比较nodes中的所有节点,分别添加children和parent
     */
    for (int i = 0; i < nodes.size(); i++) {
      Node n = nodes.get(i);
      for (int j = i + 1; j < nodes.size(); j++) {
        Node m = nodes.get(j);
        if (n.getId() == m.getpId()) {
          n.getChildrenNodes().add(m);
          m.setParent(n);
        } else if (n.getpId() == m.getId()) {
          n.setParent(m);
          m.getChildrenNodes().add(n);
        }
      }
    } 

    for (Node n : nodes) {
      setNodeIcon(n);
    }
    return nodes;
  } 

  /**
   * 设置打开,关闭icon
   *
   * @param node
   */
  public static void setNodeIcon(Node node) {
    if (node.getChildrenNodes().size() > 0 && node.isExpand()) {
      node.setIcon(R.drawable.tree_expand);
    } else if (node.getChildrenNodes().size() > 0 && !node.isExpand()) {
      node.setIcon(R.drawable.tree_econpand);
    } else
      node.setIcon(-1);
  } 

  public static void setNodeChecked(Node node, boolean isChecked) {
    // 自己设置是否选择
    node.setChecked(isChecked);
    /**
     * 非叶子节点,子节点处理
     */
    setChildrenNodeChecked(node, isChecked);
    /** 父节点处理 */
    setParentNodeChecked(node);
  } 

  /**
   * 非叶子节点,子节点处理
   */
  private static void setChildrenNodeChecked(Node node, boolean isChecked) {
    node.setChecked(isChecked);
    if (!node.isLeaf()) {
      for (Node n : node.getChildrenNodes()) {
        // 所有子节点设置是否选择
        setChildrenNodeChecked(n, isChecked);
      }
    }
  } 

  /**
   * 设置父节点选择
   *
   * @param node
   */
  private static void setParentNodeChecked(Node node) { 

    /** 非根节点 */
    if (!node.isRoot()) {
      Node rootNode = node.getParent();
      boolean isAllChecked = true;
      for (Node n : rootNode.getChildrenNodes()) {
        if (!n.isChecked()) {
          isAllChecked = false;
          break;
        }
      } 

      if (isAllChecked) {
        rootNode.setChecked(true);
      } else {
        rootNode.setChecked(false);
      }
      setParentNodeChecked(rootNode);
    }
  } 

}

核心的代码就是这些,希望对大家有帮助。

DEMO源码:http://xiazai.jb51.net/201611/yuanma/AndroidTreeView(jb51.net).rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android UI 之实现多级树形列表TreeView示例

    所谓TreeView就是在Windows中常见的多级列表树,在Android中系统只默认提供了ListView和ExpandableListView两种列表,最多只支持到二级列表的实现,所以如果想要实现三级和更多层次的列表,就需要我们自己来做一些处理了. 其实这个效果很久以前就有人想办法实现了,但是实现的效果有一些问题,我的实现思路主要也是来自于网络,但是在其基础上修正了逻辑上的一些错误,做了一些优化. 先来看一下效果: 然后大体说一下思路: 其实这里实现的多级列表只是一个视觉效果,我们看到的分

  • Android仿美团淘宝实现多级下拉列表菜单功能

    我们在常用的电商或者旅游APP中,例如美团,手机淘宝等等,都能够看的到有那种下拉式的二级列表菜单.具体如图所示: 上面两张图就是美团的一个二级列表菜单的一个展示.我相信很多人都想开发一个跟它一样的功能放到自己的APP中.好,接下来我们就开始动手,解决它. 1.结构分析 首先,我们给出这个下拉菜单需要的组建.我们用线框图来分析. 1)如上图所示,最外围的是一个Activity,顶部包含了一个View的容器,这个容器主要是装载ToggleButton来实现诸如美团里面的"美食,全城,理我最近,刷选&

  • Android多级树形列表控件

    我们开发app过程中,经常会碰到需要 多级列表展示的效果.而Android原生sdk中根本没有3级 4级甚至更多级别的列表控件. 所以我们就要自己去实现一个类似treeListView 的控件,下面这个是我项目中的一个效果图,可支持多级列表扩展. android中有ExpandListView控件,但是这个控件只支持两级列表.对于多级列表如果重写这个不是很好用. 实现这种列表 思想就是递归,构造一个子父级的关系. 话不多说 代码中体会 Activity package com.example.c

  • android开发实现列表控件滚动位置精确保存和恢复的方法(推荐)

    Android开发经常要对列表的滚动位置进行保存和恢复,网上也有很多关于此功能的方法文章,但绝大多数都只能保存恢复到某一行,对于滚动到半行的情况不能精确的恢复.也有很多文章介绍了好几种方法,也说某些方法能够精确的控制,但实际上根本不能实现.还有些介绍了很多玄乎且非常复杂的方法,但也没看到能完整实现的代码. 经过一段时间的研究测试,下面的代码可以完美的实现列表滚动位置的精确保存和恢复,而且只是在原来记忆到行位置的基础上增加了2行代码而已. 具体见下面代码和注释: //保存位置: int posit

  • Python tkinter 树形列表控件(Treeview)的使用方法

    1.方法 方法 描述 bbox(item, column=None) 返回指定item的框选范围,或者单元格的框选范围 column( cid, option=None, **kw) 设置或者查询某一列的属性 delete(*items) 删除指定行或者节点(含子节点) vdetach(*items) 与delete类似,不过不是真正删除,而是隐藏了相关内容.可以用move方法重新显示v exists(item) 判断指定的item是否存在 focus(item=None) 获得选定item的i

  • Android ExpandableListView展开列表控件使用实例

    你是否觉得手机QQ上的好友列表那个控件非常棒? 不是..... 那也没关系,学多一点知识对自己也有益无害. 那么我们就开始吧. 展开型列表控件, 原名ExpandableListView 是普通的列表控件进阶版, 可以自由的把列表进行收缩, 非常的方便兼好看. 首先看看我完成的截图, 虽然界面不漂亮, 但大家可以自己去修改界面. 该控件需要一个主界面XML 一个标题界面XML及一个列表内容界面XML 首先我们来看看 mian.xml 主界面 复制代码 代码如下: //该界面非常简单, 只要一个E

  • Android ListView列表控件的介绍和性能优化

    ListView列表控件 一.ListView显示数据的原理:mvc模式 m:mode 数据(用javabean规范封装) v:view ListView c:adapter 适配器,负责把数据展示到ListView上 二.ListView最常用适配器 BaseAdapter.SimpleAdapter.ArrayAdapter 三.ListView显示数据的步骤 1.创建ListView 2.自定义ListView的适配器继承BaseAdapter,重写baseAdapter的getCount

  • Android UI 中的 ListView列表控件的示例

    当程序中有大量的数据需要展示时,就需要用到 ListView 啦.ListView 允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕. 1 基本用法 布局文件中加入 ListView: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/and

  • Android列表控件Spinner简单用法示例

    本文实例讲述了Android列表控件Spinner简单用法.分享给大家供大家参考,具体如下: Android的Spinner控件用来显示列表项,类似于一组单选框RadioButton.这里介绍一下其简单用法: xml布局: <?xml version="1.0" encoding="utf-8"?> <LinaerLayout xmlns:android="http://schemas.android.com/apk/res/androi

  • android RecycleView实现多级树形列表

    本文实例为大家分享了android RecycleView实现多级树形列表的具体代码,供大家参考,具体内容如下 实现多级树状列表: 1. Node.java public class Node<T, B> implements Serializable { /** * 传入的实体对象 */ public B bean; /** * 设置开启的图片 */ public int iconExpand = -1; /** * 设置关闭的图片 */ public int iconNoExpand =

  • python GUI库图形界面开发之PyQt5树形结构控件QTreeWidget详细使用方法与实例

    PyQt5树形结构控件QTreeWidget简介 QTreeWidget 类根据预设的模型提供树形显示控件. QTreeWidget 使用类似于 QListView 类的方式提供一种典型的基于 item 的树形交互方法类,该类基于QT的"模型/视图"结构,提供了默认的模型来支撑 item 的显示,这些 item 类为 QTreeWidgetItem 类. 如果不需要灵活的"模型/视图"框架,可以使用QTreeWidget 来创建有层级关系的树形结构.当把标准 ite

  • Android实现字母导航控件的示例代码

    目录 自定义属性 Measure测量 坐标计算 绘制 Touch事件处理 数据组装 显示效果 今天分享一个以前实现的通讯录字母导航控件,下面自定义一个类似通讯录的字母导航 View,可以知道需要自定义的几个要素,如绘制字母指示器.绘制文字.触摸监听.坐标计算等,自定义完成之后能够达到的功能如下: 完成列表数据与字母之间的相互联动; 支持布局文件属性配置; 在布局文件中能够配置相关属性,如字母颜色.字母字体大小.字母指示器颜色等属性. 主要内容如下: 自定义属性 Measure测量 坐标计算 绘制

随机推荐