Android打造全方位滚动的ListView
来源:互联网 发布:python生成密码 编辑:程序博客网 时间:2024/05/17 05:55
在Android原有ListView控件基础之上打造一个类似于表格形式,全方位滚动(既可以上下滚动又可以左右滚动)的UDLRSlideListView控件。
一. 要实现的目标
在实现之前咱们先列出UDLRSlideListView控件要实现的目标有哪些:
- 为了扩展方便重写ListView,ListView大部分的特性我们还继续保留着,关键时候有大用处。
- ListView的行可以左右滑动,因为手机的屏幕比较小,咱们经常显示表格的时候不能保证一个屏幕全部显示完成。
- 在行可以左右滑动的基础上,咱还可以动态设置固定每一行前面多少列是不会滑动(把每一行分成左右两个部分,左边的一部分不能滑动,右边的一部可以滑动)。
- 可以设置标题,并且UDLRSlideListView上下滑动的时候标题可以一直固定在顶部(可以动态设置是否固定)。
- 下拉刷新,上拉加载功能。
二. 效果展示
三. 实现过程
为了实现UDLRSlideListView控件的所有功能,咱们把整体拆分成一个一个小的部分,依次实现,最后再拼到一起来。
3.1 固定标题栏在顶部
中心思想就是在上下滑动的过程中,把标题栏View画到ListView的顶部。这里又得分两个部分了:一个是怎么在上下滚动的过程中拿到标题栏对应的View,并且让重绘;一个怎么画到ListView的顶部。
1). 拿到标题栏,这个简单,adapter里面有getView()的方法,同时我们规定如果有标题栏的时候position=0的位置是标题栏。核心代码如下:
@Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mScrollListener != null) { mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } if (getAdapter() != null && !(getAdapter() instanceof UDLRSlideAdapter)) { return; } int headerViewCount = getHeaderViewsCount(); if (getAdapter() == null || !mPinTitle || firstVisibleItem < headerViewCount) { /** * 第一个section都还没出来 */ mLayoutTitleSection = null; for (int i = 0; i < visibleItemCount; i++) { View itemView = getChildAt(i); if (itemView != null) { itemView.setVisibility(VISIBLE); } } return; } if (getAdapter().getCount() <= 0) { return; } if (mLayoutTitleSection == null) { mLayoutTitleSection = getTitleSectionLayout(0); ensurePinViewLayout(mLayoutTitleSection); } if (mLayoutTitleSection == null) { return; } invalidate(); }
/** * 获取固定在顶部的View * * @return View */ private View getTitleSectionLayout(int adapterPosition) { if (getAdapter() == null) { return null; } /** * getView的第二个参数一定要传空,因为我们不能用复用的View */ return getAdapter().getView(adapterPosition, null, this); }
2). 拿到标题栏的View之后,就要把他绘制到ListView的顶部了。核心代码如下(其中mLayoutTitleSection是标题栏View):
@Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (getAdapter() == null || !(getAdapter() instanceof UDLRSlideAdapter) || mLayoutTitleSection == null || !mPinTitle) { return; } int saveCount = canvas.save(); canvas.clipRect(0, 0, getWidth(), mLayoutTitleSection.getMeasuredHeight()); mLayoutTitleSection.draw(canvas); canvas.restoreToCount(saveCount); }
ps: 关于固定标题栏在顶部更加具体的细节可以瞧一瞧 Android分组悬浮列表实现
3.2 上下滚动
这个容易,上下滚动我们直接用ListView自带的就好了,super,super就好了。
3.3 左右滚动
对于每一行,前面一部分不让滚动fix,后面一部分可以让滚动slide。这样我们每一行都是一个horizontal的LinearLayout了,并且含有两个LinearLayout,我们在adapter getView()获取每一行的convertView的时候,把固定的column,addView到不可滚动的LinearLayout当中去,把可以滚动的column addView到可以滚动的LinearLayout里面去。这样每一行的convertView就出来了。对每一行的convertView我们做了一个简单的封装UDLRSlideRowLayout主要是封装左右滑动的处理。因为adapter的getView()我们要提前做好处理,那我们就得在BaseAdapter的基础上打造一个UDLRSlideAdapter,核心代码如下(这里我们只是列出了UDLRSlideAdapter里面的getView()方法的实现):
@Override public View getView(int position, View convertView, ViewGroup parent) { List<T> itemData = getItem(position); UDLRSlideViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_udlr_slide, parent, false); //设置item高度 AbsListView.LayoutParams params = (AbsListView.LayoutParams) convertView.getLayoutParams(); params.height = getItemViewHeight(); convertView.setLayoutParams(params); onCrateConvertViewFinish(convertView, position); holder = new UDLRSlideViewHolder(convertView, position); UDLRSlideRowLayout rowLayout = (UDLRSlideRowLayout) convertView.findViewById(R.id.item_udls_slide_row); //组合每一行的View,包含两部分,一个是固定的LinearLayout,一个是可滑动的LinearLayout if (itemData != null && !itemData.isEmpty()) { for (int index = 0; index < itemData.size(); index++) { View columnView; if (index < mSlideStartColumn) { columnView = getColumnView(position, index, itemData.size(), rowLayout.getFixLayout()); columnView.setLayoutParams(getColumnViewParams(position, index, itemData.size())); rowLayout.getFixLayout().addView(columnView); } else { columnView = getColumnView(position, index, itemData.size(), rowLayout.getSlideLayout()); columnView.setLayoutParams(getColumnViewParams(position, index, itemData.size())); rowLayout.getSlideLayout().addView(columnView); } holder.addColumnView(index, columnView); } } convertView.setTag(holder); } else { holder = (UDLRSlideViewHolder) convertView.getTag(); //更新下holder position的位置 holder.setPosition(position); } //复用的时候不能滑倒指定位置 UDLRSlideRowLayout slideLayout = (UDLRSlideRowLayout) holder.getConvertView().findViewById(R.id.item_udls_slide_row); slideLayout.slideSet(mSlideLength); if (itemData != null && !itemData.isEmpty()) { for (int index = 0; index < itemData.size(); index++) { if (holder.getColumnView(index) != null) { convertColumnViewData(position, index, holder.getColumnView(index), convertView, itemData.get(index), itemData); } } } return convertView; }
这里稍微做一点点解释,15~29行,生成每一个row里面所有的column的View并且分成了两部分,不可以滑动的column View add到了fix layout里面,可以滑动的column View add到了slide layout里面。
到这里每一行的converView我们就已经组合好了,接下来就是捕捉左右滑动的事件了,这里为了简单一点当左右滑动的同时我们就不让上下滑动了,事件的捕捉这个应该简单吧,那咱就得对onTouchEvent()函数动刀子了,核心代码如下:
@Override public boolean onTouchEvent(MotionEvent ev) { boolean handler = false; if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain();//跟踪触摸事件滑动的帮助类 } mVelocityTracker.addMovement(ev); final int action = ev.getAction(); final float x = ev.getX(); final float y = ev.getY(); switch (action) { case MotionEvent.ACTION_MOVE: final int xDiff = (int) Math.abs(x - mLastMotionDownX); final int yDiff = (int) Math.abs(y - mLastMotionDownY); if (!mInSlideMode) { if (xDiff > mTouchSlop && xDiff > yDiff) { mInSlideMode = true; } } if (mInSlideMode) { final int deltaX = (int) (mLastMotionX - x);//滑动的距离 prepareSlideMove(deltaX); mLastMotionX = x; handler = true; } break; case MotionEvent.ACTION_UP: if (mInSlideMode) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000);//1000毫秒移动了多少像素 int velocityX = (int) velocityTracker.getXVelocity();//当前的速度 if (canSlide()) { if (Math.abs(velocityX) < SNAP_VELOCITY) { //TODO: } else { prepareFling(-velocityX); } } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } mInSlideMode = false; break; case MotionEvent.ACTION_CANCEL: mInSlideMode = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } break; } return handler || super.onTouchEvent(ev); }
UDLRSlideListView里面onTouchEvent()函数15~26行,判断是左右滑动,拿到滑动的位移,最终会调用到UDLRSlideRowLayout里面的slideMove()函数,更加详细的实现可以参考UDLRSlideRowLayout类的实现。
3.4 事件的拦截
每一行里面的某列子View要自己处理事件的时候,会和左右滑动事件冲突,那咱们就得稍微做点处理了,当左右滑动的时候,咱得把事件拦截下来,那就得对onInterceptTouchEvent函数动刀子了,核心代码如下:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float x = ev.getX(); final float y = ev.getY(); switch (action) { case MotionEvent.ACTION_MOVE: final int xDiff = (int) Math.abs(x - mLastMotionDownX); final int yDiff = (int) Math.abs(y - mLastMotionDownY); if (!mInSlideMode) { if (xDiff > mTouchSlop && xDiff > yDiff) { return true; } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: break; } return super.onInterceptTouchEvent(ev); }
ps:在实现的过程中,这里有个小插曲,也算是个小问题吧,在MotionEvent.ACTION_DOWN的时候我们肯定是要做一些初始化的,刚开始的时候我是放在onTouchEvent()函数里面做的,后来我直接放到dispatchTouchEvent()函数里面去处理了。因为当子View有事件处理的时候onTouchEvent()函数里面是走不到MotionEvent.ACTION_DOWN事件的。
3.5 下拉刷新,上拉加载
因为我们的UDLRSlideListView没有破坏ListView的特性,这样网上就有很多开源的框架来给我们实现这个功能了。例子里面用的XRefreshView实现的。
四. 源码
源码链接
五. 写在结尾的话
整个实现过程介绍的很简单,如果有相同需求的话可以扒源码下来瞧一瞧,也可以在这基础上做相应的修改,当然里面肯定也有很多不合理的地方,很多需要完善的地方。欢迎指出。
- Android打造全方位滚动的ListView
- android listview 的滚动
- Android 打造顶部停留控件,可用于所有可滚动的控件(ScrollView,ListView)
- Android - 滚动ListView的适配器
- Android 打造自己的滚动选择器ScrollSelector
- 打造万能的android ListView GridView 适配器
- Android 打造万能的ListView GridView 适配器
- Android打造万能的ListView GridView Adapter
- android 打造万能的ListView GridView 适配器
- Android 打造万能的ListView GridView 适配器
- Android之打造ListView的万能适配器
- Android ListView的滚动条样式
- Android支持横行滚动的ListView控件
- Android ListView的滚动条样式
- Android支持横行滚动的ListView控件
- Android支持横行滚动的ListView控件
- Android支持横行滚动的ListView控件
- Android ListView的滚动条样式
- angularJS之ng-class
- 最短路径—Dijkstra算法和Floyd算法
- govendor的使用
- 【JZOJ 5270】神奇的矩阵
- 丑数、丑数 II
- Android打造全方位滚动的ListView
- Linux下静态库与动态库
- 神经网络与深度学习笔记(四)为什么用交叉熵代替二次代价函数
- Java并发-线程之间的协作
- Segnet分割网络caffe教程(一)
- sqlite3常用命令&语法
- [设计模式](二):工厂模式(简单工厂|静态工程、工厂方法|多工厂、抽象工厂)
- Python中re的用法示例
- Node创建服务器对象