多级列表是个很常见的功能,发现很多代码都不好扩展,或者由于数据结构设计不好,导致开发维护比较费时间。
11月14号更新:增加了 选择按钮,可以实现选中效果。用于选择。
下面自己写了一个,github链接地址: github地址分享一波。如图所示
看一下节点的代码,最重要的设计TreePoint
public class TreePoint {
private String ID; // 7241, //账号id
private String NNAME; //名称
private String PARENTID; // 0, //父id 0表示根节点
private String ISLEAF; //0, //是否是叶子节点 1为叶子节点
private int DISPLAY_ORDER; // 1 //同一个级别的显示顺序
private boolean isExpand = false; //是否展开了
}
先看下MainActivity
public class MainActivity extends AppCompatActivity {
private ReasonAdapter adapter;
private ListView listView;
private EditText et_filter;
private List<TreePoint> reasonPointList = new ArrayList<>();
private HashMap<String, TreePoint> reasonMap = new HashMap<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView();
init();
addListener();
}
public void setContentView() {
setContentView(R.layout.activity_main);
}
public void init() {
adapter = new ReasonAdapter(this, reasonPointList, reasonMap);
listView = findViewById(R.id.listView);
listView.setAdapter(adapter);
et_filter = findViewById(R.id.et_filter);
initData();
}
//初始化数据
//数据特点:TreePoint 之间的关系特点 id是任意唯一的。 如果为根节点 PARENTID 为"0" 如果没有子节点,也就是本身是叶子节点的时候ISLEAF = "1"
// DISPLAY_ORDER 是同一级中 显示的顺序
//如果需要做选中 单选或者多选,只需要给TreePoint增加一个选中的属性,在ReasonAdapter中处理就好了
private void initData() {
reasonPointList.clear();
int id =1000;
int parentId = 0;
int parentId2 = 0;
int parentId3 = 0;
for(int i=1;i<5;i++){
id++;
reasonPointList.add(new TreePoint(""+id,"分类"+i,"" + parentId,"0",i));
for(int j=1;j<5;j++){
if(j==1){
parentId2 = id;
}
id++;
reasonPointList.add(new TreePoint(""+id,"分类"+i+"_"+j,""+parentId2,"0",j));
for(int k=1;k<5;k++){
if(k==1){
parentId3 = id;
}
id++;
reasonPointList.add(new TreePoint(""+id,"分类"+i+"_"+j+"_"+k,""+parentId3,"1",k));
}
}
}
//这里打乱数据的顺序,模拟数据乱序,重新排序
Collections.shuffle(reasonPointList);
updateData();
}
public void addListener() {
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
adapter.onItemClick(position);
}
});
et_filter.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
searchAdapter(s);
}
});
}
private void searchAdapter(Editable s) {
adapter.setKeyword(s.toString());
}
//对数据排序 深度优先
private void updateData() {
//这里比较重要的是 reasonPointList 这个集合中的内容又使用 reasonMap这个HashMap保存起来,主要是为了利用ArrayList遍历快,HashMap查找快的特征。提高代码的执行效率。
for (TreePoint reasonTreePoint : reasonPointList) {
reasonMap.put(reasonTreePoint.getID(), reasonTreePoint);
}
Collections.sort(reasonPointList, new Comparator<TreePoint>() {
@Override
public int compare(TreePoint lhs, TreePoint rhs) {
int llevel = TreeUtils.getLevel(lhs, reasonMap);
int rlevel = TreeUtils.getLevel(rhs, reasonMap);
if (llevel == rlevel) {
if (lhs.getPARENTID().equals(rhs.getPARENTID())) { //左边小
return lhs.getDISPLAY_ORDER() > rhs.getDISPLAY_ORDER() ? 1 : -1;
} else { //如果父辈id不相等
//同一级别,不同父辈
TreePoint lreasonTreePoint = TreeUtils.getTreePoint(lhs.getPARENTID(), reasonMap);
TreePoint rreasonTreePoint = TreeUtils.getTreePoint(rhs.getPARENTID(), reasonMap);
return compare(lreasonTreePoint, rreasonTreePoint); //父辈
}
} else { //不同级别
if (llevel > rlevel) { //左边级别大 左边小
if (lhs.getPARENTID().equals(rhs.getID())) {
return 1;
} else {
TreePoint lreasonTreePoint = TreeUtils.getTreePoint(lhs.getPARENTID(), reasonMap);
return compare(lreasonTreePoint, rhs);
}
} else { //右边级别大 右边小
if (rhs.getPARENTID().equals(lhs.getID())) {
return -1;
}
TreePoint rreasonTreePoint = TreeUtils.getTreePoint(rhs.getPARENTID(), reasonMap);
return compare(lhs, rreasonTreePoint);
}
}
}
});
adapter.notifyDataSetChanged();
}
}
适配器 中通过缩进控制不同的级别
public class ReasonAdapter extends BaseAdapter {
private Context mcontext;
private Activity mActivity;
private List<TreePoint> reasonPointList;
private String keyword = "";
private HashMap<String, TreePoint> reasonMap = new HashMap<>();
public ReasonAdapter(final Context mcontext, List<TreePoint> reasonPointList, HashMap<String, TreePoint> reasonMap) {
this.mcontext = mcontext;
this.mActivity = (Activity) mcontext;
this.reasonPointList = reasonPointList;
this.reasonMap = reasonMap;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
//全部闭合,然后展开
for (TreePoint reasonTreePoint : reasonPointList) {
reasonTreePoint.setExpand(false);
}
if (!"".equals(keyword)) {
for (TreePoint reasonTreePoint : reasonPointList) {
if (reasonTreePoint.getNNAME().contains(keyword)) {
//含有keyword
if ("0".equals(reasonTreePoint.getISLEAF())) {
reasonTreePoint.setExpand(true);
}
//打开所有的父级元素
openExpand(reasonTreePoint);
}
}
}
this.notifyDataSetChanged();
}
private void openExpand(TreePoint reasonTreePoint) {
if ("0".equals(reasonTreePoint.getPARENTID())) {
reasonTreePoint.setExpand(true);
} else {
reasonMap.get(reasonTreePoint.getPARENTID()).setExpand(true);
openExpand(reasonMap.get(reasonTreePoint.getPARENTID()));
}
}
//第一要准确计算数量
@Override
public int getCount() {
int count = 0;
for (TreePoint tempPoint : reasonPointList) {
if ("0".equals(tempPoint.getPARENTID())) {
count++;
} else {
if (getItemIsExpand(tempPoint.getPARENTID())) {
count++;
}
}
}
return count;
}
private boolean getItemIsExpand(String ID) {
for (TreePoint tempPoint : reasonPointList) {
if (ID.equals(tempPoint.getID())) {
if (tempPoint.isExpand()) {
return true;
} else {
return false;
}
}
}
return false;
}
@Override
public Object getItem(int position) {
return reasonPointList.get(convertPostion(position));
}
private int convertPostion(int position) {
int count = 0;
for (int i = 0; i < reasonPointList.size(); i++) {
TreePoint treePoint = reasonPointList.get(i);
if ("0".equals(treePoint.getPARENTID())) {
count++;
} else {
if (getItemIsExpand(treePoint.getPARENTID())) {
count++;
}
}
if (position == (count - 1)) {
return i;
}
}
return 0;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(mcontext).inflate(R.layout.adapter_treeview, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
holder.contactitemBtn = (ImageButton) convertView.findViewById(R.id.contactitem_sendmsg);
holder.contactitemBtn.setVisibility(View.GONE);
convertView.setTag(holder);
convertView.setBackgroundColor(0xffffffff);
convertView.setPadding(DensityUtil.dip2px(mcontext, 10), DensityUtil.dip2px(mcontext, 10), DensityUtil.dip2px(mcontext, 10), DensityUtil.dip2px(mcontext, 10));
} else {
holder = (ViewHolder) convertView.getTag();
}
TreePoint tempPoint = (TreePoint) getItem(position);
int level = TreeUtils.getLevel(tempPoint, reasonMap);
holder.icon.setPadding(25 * level, holder.icon.getPaddingTop(), 0, holder.icon.getPaddingBottom());
if ("0".equals(tempPoint.getISLEAF())) {
if (tempPoint.isExpand() == false) {
holder.icon.setVisibility(View.VISIBLE);
holder.icon.setImageResource(R.drawable.outline_list_collapse);
} else {
holder.icon.setVisibility(View.VISIBLE);
holder.icon.setImageResource(R.drawable.outline_list_expand);
}
} else {
holder.icon.setVisibility(View.INVISIBLE);
holder.icon.setImageResource(R.drawable.outline_list_collapse);
}
if (keyword != null && !"".equals(keyword) && tempPoint.getNNAME().contains(keyword)) {
int index = tempPoint.getNNAME().indexOf(keyword);
int len = keyword.length();
Spanned temp = Html.fromHtml(tempPoint.getNNAME().substring(0, index)
+ "<font color=#FF0000>"
+ tempPoint.getNNAME().substring(index, index + len) + "</font>"
+ tempPoint.getNNAME().substring(index + len, tempPoint.getNNAME().length()));
holder.text.setText(temp);
} else {
holder.text.setText(tempPoint.getNNAME());
}
holder.text.setCompoundDrawablePadding(DensityUtil.dip2px(mcontext, 10));
holder.contactitemBtn.setSelected(false);
if (tempPoint.isExpand())
holder.contactitemBtn.setSelected(true);
return convertView;
}
public void onItemClick(int position) {
TreePoint reasonTreePoint = (TreePoint) getItem(position);
if ("1".equals(reasonTreePoint.getISLEAF())) {
//处理回填
Toast.makeText(mcontext, getSubmitResult(reasonTreePoint), Toast.LENGTH_SHORT).show();
} else { //如果点击的是父类
if (reasonTreePoint.isExpand()) {
for (TreePoint tempPoint : reasonPointList) {
if (tempPoint.getPARENTID().equals(reasonTreePoint.getID())) {
if ("0".equals(reasonTreePoint.getISLEAF())) {
tempPoint.setExpand(false);
}
}
}
reasonTreePoint.setExpand(false);
} else {
reasonTreePoint.setExpand(true);
}
}
this.notifyDataSetChanged();
}
private String getSubmitResult(TreePoint reasonTreePoint) {
StringBuilder sb = new StringBuilder();
addResult(reasonTreePoint, sb);
String result = sb.toString();
if (result.endsWith("-")) {
result = result.substring(0, result.length() - 1);
}
return result;
}
private void addResult(TreePoint reasonTreePoint, StringBuilder sb) {
if (reasonTreePoint != null && sb != null) {
sb.insert(0, reasonTreePoint.getNNAME() + "-");
if (!"0".equals(reasonTreePoint.getPARENTID())) {
addResult(reasonMap.get(reasonTreePoint.getPARENTID()), sb);
}
}
}
class ViewHolder {
TextView text;
ImageView icon;
ImageButton contactitemBtn; //这是个选择的按钮,可以自己适当修改代码实现功能
}
}
工具类 获取层级,其实这里可以将TreePoint增加一个层级属性,如果层级属性为空,就重新计算,如果不为空,就直接使用,避免滚动listView的时候多次计算。
public class TreeUtils {
//第一级别为0
public static int getLevel(TreePoint treePoint,HashMap<String,TreePoint> map){
if("0".equals(treePoint.getPARENTID())){
return 0;
}else{
return 1+getLevel(getTreePoint(treePoint.getPARENTID(),map),map);
}
}
public static TreePoint getTreePoint(String ID, HashMap<String,TreePoint> map){
if(map.containsKey(ID)){
return map.get(ID);
}
Log.e("xlc","ID:" + ID);
return null;
}
}
以上大致就是所有的代码了。主要是为了实现多级列表。方便扩展。要保证效率。