详解Android 多级联动控件实现思路讨论

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

所以就需要一个多级联动选择控件 ,在网上一番搜索或找到了这个控件, 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 多级联动内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android实现省市区三级联动

    针对AdapterView的拓展使用,Spinner实现省市区的三级联动,具体内容如下 其主要是通过使用Spinner的setOnItemSelectListener来实现. 代码示例: activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"

  • android实现下拉菜单三级联动

    android中的下拉菜单联动应用非常普遍,android中的下拉菜单用Spinner就能实现,以下列子通过简单的代码实现三级菜单联动. 一 样式文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_p

  • Android实现联动下拉框二级地市联动下拉框功能

    日常使用软件中,为了方便且规范输入,会使用到下拉框进行输入,如注册时生日选项,购物时的地址输入,都会用到下拉框,今日笔者为了巩固已学的知识,实现了二级联动下拉框用作回顾及分享给求知的新手. 思路/步骤: 在实现联动下拉框之前,我们先对用到的ArrayAdapter和数据的封装作必要的了解,Android 中提供了很多适配器的实现类,其中ArrayAdapter就其中之一.它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入.如: ArrayAdapter<CharSequen

  • 最好用的Android省市区三级联动选择效果

    Android省市区选择三级联动效果,一个不大不小的功能,就算你做过,但是没有相关的代码直接写,也要花掉你至少半天时间. 下面我写出我的实现过程(思路绝对清晰). 先上效果图 一.准备数据 我是用的本地的json数据(走网络的话太慢,每次都要请求),放在asserts中.格式如下: [{ "name": "河北省", "city": [ { "name": "石家庄市", "area":

  • Android自定义WheelView地区选择三级联动

    本文实例为大家分享了WheelView地区选择三级联动的具体代码,供大家参考,具体内容如下 1. 效果 最近需要做一个地区选择的功能,但是在网上和github上找了很久都没找到满意的,然后朋友推荐了一个给我,我花了点时间把代码大致看懂并改成我想要的,并写上我的理解.效果如图: 2. 注意 a. 首先我们要明白,网上这写三级联动的demo,不管是把数据库文件放在raw还是assets中,我们都要进行复制,将这个文件复制到app目录下,即 /data/data/"+context.getPackag

  • Android使用android-wheel实现省市县三级联动

    今天没事跟群里面侃大山,有个哥们说道Android Wheel这个控件,以为是Andriod内置的控件,google一把,发现是个github上的一个控件. 下载地址:https://code.google.com/p/android-wheel/    发现很适合做省市县三级联动就做了一个. 先看下效果图: 1.首先导入github上的wheel项目 2.新建个项目,然后选择记得右键->Properties->Android中将wheel添加为lib: 上面两个步骤是导入所有开源项目的过程了

  • Android省市区三级联动控件使用方法实例讲解

    最近有需求需要实现省市区三级联动,但是发现之前的实现不够灵活,自己做了一些优化.为了方便以后使用,抽离出来放在了github上WheelView.同时把其核心库放在了JCenter中了,可以直接引用.也可以参考项目中的Demo进行引用 下面介绍一下如何使用 如果用的是AndroidStudio那么直接在build.gradle文件中添加依赖: dependencies { compile 'chuck.WheelItemView:library:1.0.1' } 成功引入库之后,可以在需要弹出省

  • Android日期选择器实现年月日三级联动

    最近项目里面用到了一个日期选择器,实现现在主流的WheelView滑动选择,整理了下,做了个Demo.废话不多说,直接上代码. 主布局:activity_main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools=&quo

  • android-wheel控件实现三级联动效果

    本文实例为大家分享了android wheel省市县三级联动效果,供大家参考,具体内容如下 在github上面有一个叫做 Android-wheel 的开源控件, 代码地址:https://github.com/maarek/android-wheel 源码下载地址:http://xiazai.jb51.net/201610/yuanma/AndroidCascadeMaster(jb51.net).rar 主界面布局 activity_main.xml <LinearLayout xmlns:

  • 详解Android 多级联动控件实现思路讨论

    最近有一个需求是选择多级联动数据,数据级别不固定,可能是五级,可能是两级,具体看用户等级. 所以就需要一个多级联动选择控件 ,在网上一番搜索或找到了这个控件, Android-PickerView 这个控件在三级以内的的联动都没有问题,但是最多只能到三级. 我在原有的基础上做了一些扩展,主要是添加了两个 picker MultiWheelPickerView 可以根据数据动态生成多个滚轮,不再局限于两个三个选项 DynamicWheelPickerView 也是动态生成,但可以一级一级的加载数据

  • 详解BootStrap中Affix控件的使用及保持布局的美观的方法

    Affix是BootStrap中的一个很有用的控件,他能够监视浏览器的滚动条的位置并让你的导航始终都在页面的可视区域.一开始的时候,导航在页面中是普通的流式布局,占有文档中固定的位置,当页面滚动的时候,导航就自动变成了固定布局(fixed),始终处于用户的视区,下面来说说他的用法.首先来看看他的实现原理.它是通过实时修改页面元素的class属性来实现的 开始的时候应用affix的元素的class中会自动添加affxi-top属性 当滚动条滚动以至于导航快要到页面顶部的时候这时候在元素的class

  • 详解WPF双滑块控件的使用和强制捕获鼠标事件焦点

    目录 效果 概述 代码部分 效果 概述 最近有个小需求要用双滑块表示一个取值范围,于是就简单做了个用户控件,在此记录下. 使用矩形Rectangle表示范围,椭圆Ellipse表示滑块,使用Canvas控制滑块的左右移动. 椭圆的鼠标按下事件里强制获取鼠标事件焦点,避免移动过快或移出控件范围时,滑块就不跟着跑了.椭圆的鼠标抬起事件释放强制获取鼠标事件焦点 代码部分 需求比较简单,只定义了4个依赖属性,范围的最大值和最小值,取值的最大值和最小值. 接下来就是计算滑块和高亮矩形的位置,计算时注意减去

  • 详解ASP.NET-----Repeater数据控件的用法总结

    一.Repeater控件的用法流程及实例: 1.首先建立一个网站,新建一个网页index.aspx. 2.添加或者建立APP_Data数据文件,然后将用到的数据库文件放到APP_Data文件夹中. 3.打开数据库企业管理器,数据库服务器为local(.),然后将APP_Data文件夹中的数据库附加到数据库服务器中. 4.添加Ling to  SQL类. 5.打开视图,服务器资源管理器,右击数据库服务器,选择添加连接,然后选择数据库服务器.数据库类型,及数据库表,然后完成. 6.将需要用到的表,全

  • 实例详解jQuery结合GridView控件的使用方法

    jQuery是一种非常强大的客户端JS编程技术,这里不想过多阐述它的相关背景知识,只想简单演示一下如何与asp.net的控件结合开发. 比如,我们要做一个下面如图所示的功能,效果是状态.编号.数字1.数字2.平均值所有的项都是通过后台绑定,如何点击checkbox按钮,来实现自动计算当前行两个数字的平均值呢?前提是用jQuery来实现? 我们直接在页面的Page_Load事件中输入如下代码: protected void Page_Load(object sender, EventArgs e)

  • 详解vue-element Tree树形控件填坑路

    通过tree树形控件的default-checked-keys属性来设置默认选中的节点 html.vue <el-form-item label="角色权限:"> <el-tree :data="data2" show-checkbox node-key="id" @check="handleNodeClick" :default-expanded-keys="[]" ref="

  • android之SeekBar控件用法详解

    MainActivity.java package com.example.mars_2400_seekbar; import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBar; import android.support.v4.app.Fragment; import android.app.Activity; import android.os.Bundle; import a

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

    Android ListView里控件添加监听方法的实例详解 关于ListView,算是android中比较常见的控件,在ListView我们通常需要一个模板,这个模板指的不是住模块,而是配置显示在ListView里面的东西,今天做项目的时候发现想要添加一个ImageView监听方法,发现崩了,也许是好久没有动ListView竟然忘了不能直接在主UI的xml文件里面调用其他xml文件的控件,哪怕ListView用的是这个xml文件. [错误示范]: 直接调用ImageView这个控件是ListV

  • android之RatingBar控件用法详解

    MainActivity.java package com.example.mars_2500_ratingbar; import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBar; import android.support.v4.app.Fragment; import android.app.Activity; import android.os.Bundle; import

  • 详解Android 裸眼3D效果View控件

    描述:这是一个裸眼3D效果的控件View. Tips:本项目代码部分逻辑参考于其他文章(自如的3D裸眼实现),众人拾柴火焰高,希望大家能多多补充. 项目代码:https://gitee.com/jiugeishere/uidesign 控件效果如下: 实现功能: 实现三层图片叠加效果(裸眼3D效果) 可设置每层图片移动速率 可设置每层图片移动的限制度数 可直接设置图片或引入图片 设计核心: 主要的设计核心是依赖于传感器对手机晃动的监听(重力感应监听器),对每层图片进行不同的移动,实现仿3D效果.

随机推荐