最近有一个需求是选择多级联动数据,数据级别不固定,可能是五级,可能是两级,具体看用户等级。

所以就需要一个多级联动选择控件 ,在网上一番搜索或找到了这个控件, Android-PickerView

这个控件在三级以内的的联动都没有问题,但是最多只能到三级。

我在原有的基础上做了一些扩展,主要是添加了两个 picker

MultiWheelPickerView 可以根据数据动态生成多个滚轮,不再局限于两个三个选项 DynamicWheelPickerView 也是动态生成,但可以一级一级的加载数据并追加滚轮。

在使用时,根据自身情况让你的 JavaBean 实现 IWheelItem 或者 IDynamicWheelItem 就好。

这里记录并分享一下我的思路和实现,也希望能和大家一起讨论更好的实现方案。

起初,只是想根据获取到的数据动态的生成滚轮,有多少级就生成多少个,自动排列出来就好。

在看了源码后发现原来的 OptionsPickerView 里写死了三个 WheelView ,所以最多只能是三个。

如果想动态生成 WheelView 就不能写死,只能根据数据生成,所以我选择使用代码创建 WheelView,不使用 layout 布局固定数量了。

除了 WheelView 部分外,其他部分还都是使用原来的布局。

因为要动态显示数据,就不能使用原来的 IPickerViewData 了,使用了一个新的 IWheelItem

public interface IWheelItem {

  /**
   *
   * @return 显示在滚轮的文本
   */
  String getShowText();

  /**
   *
   * @return 下一级的数据
   */
  <T extends IWheelItem> List<T> getNextItems();

}

只有两个方法,返回显示数据用来显示在滚轮上;在选择了一级后自动获取下一级内容显示。

这种多级联动的数据,明显有着上下级关系,我就默认为这种结构了,一级套着一级。

并在 WheelView 里做了调整

/**
   * 获取所显示的数据源
   *
   * @param item data resource
   * @return 对应显示的字符串
   */
  private String getContentText(Object item) {
    if (item == null) {
      return "";
    } else if (item instanceof IPickerViewData) {
      return ((IPickerViewData) item).getPickerViewText();
    } else if (item instanceof Integer) {
      //如果为整形则最少保留两位数.
      return getFixNum((int) item);
    }else if (item instanceof IWheelItem){
      return ((IWheelItem)item).getShowText();
    }
    return item.toString();
  }

First of all, 确定数据的层级,根据层级决定生成 WheelView 的数量。

/**
   * 获取当前 list 的层级,最深有多少层
   * 需要根据层级确定多少个滚轮
   * @param list 数据
   * @return 最深层级
   */
  private int getLevel(List<T> list) {
    int level = 0;
    if (list != null && list.size() > 0) {
      level = 1;
      int childLevel = 0;
      for (T code : list) {
        List<T> children =code.getNextItems();
        int temp = getLevel(children);
        if (temp > childLevel) {
          childLevel = temp;
        }
      }
      level  = childLevel;
    }
    return level;
  }

我使用的是一个 LinearLayout 横向排列,用来承载动态生成的 WheelView 。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:orientation="vertical">

  <include
    layout="@layout/include_pickerview_topbar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/pickerview_topbar_height" />

  <LinearLayout
    android:id="@ id/ll_multi_picker"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:gravity="center"
    android:minHeight="180dp"
    android:orientation="horizontal">
  </LinearLayout>
</LinearLayout>

注意:这里有一个问题就是,如果生成的滚轮很多,会显得比较拥挤。

知道了要生成多少个滚轮后,代码创建直接添加到 LinearLayout 里了。

int level =getLevel(wheelItems);
if (level > 0) {
  //生成 滚轮
  for (int i = 0; i < level; i  ) {
    WheelView wheelView = generateWheel();
    mLlContainer.addView(wheelView);
  }
  //为滚轮赋值 ,都取第一个赋值
  initWheel(wheelItems, 0);
}

生成 WheelView 之后,就是给控件赋值了,我这里默认取第一个当做选中的值。

只要前边一级选中了,那就获取它的下一级数据给下一个控件赋值,如此递归到最后一个。

protected void initWheel(List<T> list, int wheelIndex) {
    WheelView wheelView = (WheelView) mLlContainer.getChildAt(wheelIndex);
    if (null == wheelView) {
      Log.d(MultiWheelPickerView.class.getSimpleName(), "initWheel: 超出了范围 "   wheelIndex   " > "   mLlContainer.getChildCount());
      return;
    }
    if (null != list && list.size() > 0) {
      wheelView.setAdapter(new MultiWheelAdapter(list));
      wheelView.setCurrentItem(0);
      wheelView.setOnItemSelectedListener(new MultiWheelItemSelector(list, wheelIndex));
      //默认选中第一项,添加到结果里。
      T wheelItem = list.get(0);
      addToResult(wheelItem, wheelIndex);
      List<T> children = list.get(0).getNextItems();
      //有子集,继续添加
      wheelIndex  ;
      initWheel(children, wheelIndex);
    }else{
      for (int i=wheelIndex;i<mLlContainer.getChildCount();i  ){
        wheelView = (WheelView) mLlContainer.getChildAt(i);
        wheelView.setAdapter(new MultiWheelAdapter(null));
      }
    }
  }

关于选中的数据和事件,和原来一样,只是换了一种形式,使用 List 容器。

按照顺序,把选中的数据都列在里面了,逻辑如下

protected void addToResult(T value, int index) {
    // 检测是否发生了变化,需要对外释放信号
    int size = resultList.size();
    Log.d(MultiWheelPickerView.class.getSimpleName(), "addToResult: "   index   "-->"   value   "; size->"   size);
    //上级换了人,下级全部移除掉
    while (index < size) {
      resultList.remove(index);
      size = resultList.size();
    }
    //已经把之后的删除了,直接添加就行了
    boolean isAddToResult =true;
    if (null!=listener){
    // 这里可以从外部判断是否可以选择,有的 是不需要选择的,例如 all, 或者 “”
      isAddToResult = listener.isAddToResult(value);
    }
    if (isAddToResult) {
      resultList.add(value);
    }
    if (null!=listener){
      listener.onChange(resultList);
    }
  }

就这样稍微改一改,一个动态多级关联控件就有了,在使用时,让你的 JavaBean 实现 IWheelItem 就好。

简单使用方式如下

MultiWheelPickerView<CodeTable> fixedPickerView;

  private void fixedPicker() {
    if (null == fixedPickerView) {
      MultiWheelPickerBuilder<CodeTable> builder = new MultiWheelPickerBuilder<>(this,
          new MultiWheelSelectListener<CodeTable>() {
            @Override
            public void onChange(List<CodeTable> result) {
              //在滚轮选择发生变化时会被调用
              showChange(result);
            }

            @Override
            public void onSelect(List<CodeTable> result) {
              //在按下确定按钮时会被调用
              StringBuffer buffer = new StringBuffer();
              int size = result.size();
              for (int i = 0; i < size; i  ) {
                if (i != 0) {
                  buffer.append("->");
                }
                buffer.append(result.get(i).getShowText());
              }
              mTvResult.setText(buffer.toString());
            }

            @Override
            public boolean isAddToResult(CodeTable selectValue) {
              //此方法返回值会确定这个值是否可以被选中
              return !selectValue.getCode().equalsIgnoreCase("all");
            }
          });
      fixedPickerView = builder.build();
      fixedPickerView.setTitleText("行政区划");
      fixedPickerView.setWheelItems(getPickerData());
    }
    fixedPickerView.show();
  }

虽然实现了多级联动,但是在实际使用时又发现了不可忽视的问题: 如果数据过多,就会加载很长时间,从省级到村级,会有数万条记录,一次获取过来体验太差了,而且有崩溃的风险。

更好的办法是一级一级的去获取数据,选中省级再去获取下属的市级并追加滚轮显示,选中市级再去获取县级,如此类推。

So, 接续改,因为数据也是多次获取了,就无法确定层级了,故需要每有新的层级时添加新的 WheelView 追加到显示容器里(突然增加一个View会出现横跳的情况,最好是加入一个动画平滑一点)。

在选中一个数据时,也要判断是否需要去加载下一级,在我的需求里,有的是需要到村级,有的则需要到县级。

所以具体是否要加载下一级的配置要放出来,我这里放在了数据接口上,由数据自身判断。

在 IWheelItem 的基础上扩展了一个 IDynamicWheelItem

public interface IDynamicWheelItem extends IWheelItem {
  /**
   * @return 是否需要加载下一级
   */
  boolean isLoadNext() ;
}

然后是在生成 WheelView 这里做了一些修改,根据传入的数据生成。

也是默认选择了第一项,如果能被选中,则继续生成或者去加载子级数据。

protected void generateWheel(List<T> data) {
    if (data != null && data.size() > 0) {
      //需要生成 wheel
      WheelView wheelView = generateWheel();
      wheelView.setAdapter(new ArrayWheelAdapter(data));
      mLlContainer.addView(wheelView);
      int level = mLlContainer.getChildCount() - 1;
      wheelView.setOnItemSelectedListener(new DynamicWheelItemSelector(data, level));
      T iWheelItem = data.get(0);
      addToResult(iWheelItem, level);
      if (canSelect(iWheelItem)) {
        List<T> nextItems = iWheelItem.getNextItems();
        if (null != nextItems && nextItems.size() > 0) {
          generateWheel(nextItems);
        } else {
          if (iWheelItem.isLoadNext()) {
            loadNext(iWheelItem,   level);
          }
        }
      }

    }
  }

在选中一个数据后的滚轮赋值也做了修改,如果是判断是否需要去加载下一级数据或者是否现有数据

在后续没有数据的情况下,也没有移除掉 WheelView 。一旦没有数据就移除,会出现左右横跳的情况(这里也可以做一个动画,会显得没有那么突兀)。

/**
   * 设置下级Wheel 的数据
   *
   * @param current 数据
   * @param nextLevel  下一层
   */
  private void setupChildWheel(T current, int nextLevel) {
    if (mLlContainer.getChildCount() == nextLevel) {
      if (current.isLoadNext()) { //最后一级了,但是下一级仍然需要显示
        loadNext(current, nextLevel);
      }
      return;
    }
    List<T> nextItems = current.getNextItems();
    //对于下级wheel的设置上对应的数据,即使没有那么多级的,也不能移除view,只能将数据设置为null
    WheelView wheelView = (WheelView) mLlContainer.getChildAt(nextLevel);
    if (null != nextItems && nextItems.size() > 0) {
      //有子集
      //在 level ==count 时可能为空
      if (wheelView == null) {
        wheelView = generateWheel();
      }
      wheelView.setAdapter(new ArrayWheelAdapter(nextItems));
      wheelView.setCurrentItem(0);
      wheelView.setOnItemSelectedListener(new DynamicWheelItemSelector(nextItems, nextLevel));
      T wheelItem = nextItems.get(0);
      addToResult(wheelItem, nextLevel);
      nextLevel  ;
      if (canSelect(wheelItem)) {
        setupChildWheel(wheelItem, nextLevel);
      }else{ //当前已经不能选择了,之后的滚轮数据也必须置空
        for (int i = nextLevel; i < mLlContainer.getChildCount(); i  ) {
          wheelView = (WheelView) mLlContainer.getChildAt(i);
          wheelView.setOnItemSelectedListener(null);
          wheelView.setAdapter(new MultiWheelAdapter(null));
        }
      }
    } else {
      //还需要判断是否需要再次去获取子集。
      //没有子集 全部置空
      for (int i = nextLevel; i < mLlContainer.getChildCount(); i  ) {
        wheelView = (WheelView) mLlContainer.getChildAt(i);
        wheelView.setOnItemSelectedListener(null);
        wheelView.setAdapter(new MultiWheelAdapter(null));
      }
      //没有数据,需要去加载
      if (canSelect(current)&&current.isLoadNext()) {
        loadNext(current, nextLevel);
      }
    }
  }

在加载数据成功后,要将数据追加到对应的滚轮上

public void appendWheel(List<T> list, int level) {
    WheelView wheelView = null;
    if (level < mLlContainer.getChildCount()) {
      wheelView = (WheelView) mLlContainer.getChildAt(level);
    } else {
      wheelView = generateWheel();
      if (null != list && list.size() > 0)
        mLlContainer.addView(wheelView);
    }
    if (null != list && list.size() > 0) {
      wheelView.setAdapter(new MultiWheelAdapter(list));
      wheelView.setCurrentItem(0);
      T codeTable = list.get(0);
      addToResult(codeTable,level);
      wheelView.setOnItemSelectedListener(new DynamicWheelItemSelector(list, level));
      if (canSelect(codeTable)) { //合法数据,能被选择。
        //需要加载下一级
        level  ;
        setupChildWheel(codeTable,level);
      }

    }
  }

至此,改完了,比之前那个多放出来两个方法。

在侦听器里扩展了一个加载下级的方法。

public interface DynamicWheelSelectListener<T extends IDynamicWheelItem>extends MultiWheelSelectListener<T> {
  /**
   * 加载下一级的数据
   * @param item 当前数据
   * @param nextLevel 下一级的层级
   */
  void loadNextItems(T item, int nextLevel);
}

使用办法和上面的 MultiWheelPickerView 大同小异

DynamicWheelPickerView<CodeTable> dynamicPickerView;
  private void dynamicPicker() {
    if (null == dynamicPickerView) {
      dynamicPickerView =new DynamicWheelPickerBuilder<CodeTable>(this,new DynamicWheelSelectListener<CodeTable>() {
        @Override
        public void loadNextItems(CodeTable item, int nextLevel) {
          //这里模拟的数据,在加载后将 isLoadNext 设置为 false。
          List<CodeTable> child = getChild(random());
          item.setChildren(child);
          item.setLoadNext(false);
          //将数据赋值到对应的控件上,nextLevel就是控件的位置。
          dynamicPickerView.appendWheel(child, nextLevel);
        }

        @Override
        public void onChange(List<CodeTable> result) {
          showChange(result);
        }

        @Override
        public void onSelect(List<CodeTable> result) {
          StringBuffer buffer = new StringBuffer();
          int size = result.size();
          for (int i = 0; i < size; i  ) {
            if (i != 0) {
              buffer.append("->");
            }
            buffer.append(result.get(i).getShowText());
          }
          mTvResult.setText(buffer.toString());
        }

        @Override
        public boolean isAddToResult(CodeTable selectValue) {
          //是 0 的不能被选择
          return !selectValue.getCode().equalsIgnoreCase("0");
        }
      })
          .build();
      dynamicPickerView.setTitleText("行政区划");
      dynamicPickerView.setWheelItems(getChild(random()));

    }
    dynamicPickerView.show();
  }

具体用法可以看代码,在这里 TestMultiWheelActivity

其他想法:

  • 目前使用 LinearLayout 包裹的,是否可以换成 RecyclerView 呢,是否能更好的控制在一行超出多少个后换行,避免拥挤。
  • 目前在动态追加滚轮时是很生硬的追加上去的,可以优化为使用动画平滑的过渡可能体验更好些。

目前把代码放在了这里 Android-PickerView

我的实现方式就是这样,希望能和大家讨论更好的方式。

到此这篇关于详解Android 多级联动控件实现思路讨论的文章就介绍到这了,更多相关Android 多级联动内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

详解Android 多级联动控件实现思路讨论的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部