ListView实现类似WheelView效果的探究
来源:互联网 发布:java值传递 编辑:程序博客网 时间:2024/04/28 22:09
不得不说,作为一名安卓码农,总是会有蛋蛋的忧伤,因为CP常说的就是:你看,人家ios的那个效果好炫酷,比如下面这样的
作为一名合格的码农,实在不能忍,最后还是实现了这个效果,虽然没有ios的厉害。。。
实现的思路还是不复杂的,主要分两个方向:WheelView类似的思想(github一大堆)、ClipToPadding和ClipChildren取巧。因为我是用的取巧,所以我们下面只谈第二种方法。
难点有两个,一是精确地控制listview的item滚动到悬浮框内。因为大多数时候都不会是某个item刚好在悬浮框内的,但是唯一要考虑的情况也只有一种,即悬浮框内同时出现2个或多个item(设计需要,item的高度都是大于等于悬浮框的高度的,所以悬浮框内最多同时出现2个item,小于悬浮框的高度就会出现问题),取出最适合停留的item(这是个相对概念,大家可以修改代码扩展),我目前需要的就是item中间位置的y值和悬浮视图中间位置的y值最接近的一项,后面的代码里大家会看到如何处理的;
二是当listview只有很少的项时,怎么让listview可以滚动呢?这就需要ClipToPadding和ClipChildren来帮忙了。不知道这两个属性的童鞋可以上网查一查,我相信你会受益良多。ClipToPadding=false,我对这个属性简单理解就是,当你为listview设置了padingTop、padingBottom属性,listview会有pading的效果,但是当你滑动listview到顶部或底部时,listview的顶部或底部却不会出现pading的那部分区域,即listview的内容不再被pading的区域遮盖。ClipChildren=false,就是子视图可以超出父视图区域进行绘制。有了这两个属性,就可以解决listview item比较少时不能滚动的问题了。
由于设计需要滚动时item视图的放大和缩小,所以把处理放到了onscrolllistener的onscroll方法里,最后处理listview精确滚动的代码则要放到onscrolllistener的onScrollStateChanged方法里,这些都是可以修改的。
因为要实现这个效果需要adapter配合,所以代码也比较乱,但是注释还是比较详细的,大家慢慢看。
先看看WheelListView
package com.ykbjson.demo.customview.listview;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.widget.AbsListView;import android.widget.ListView;import com.ykbjson.demo.tools.SLog;/** * 包名:com.ykbjson.demo.customview.listview * 描述:类似WheelView的ListView * 创建者:yankebin * 日期:2016/6/1 */public class WheelListView extends ListView { private static final int MAX_Y_OVERSCROLL_DISTANCE = 200; private final Object SCROLL_LOCK = new Object(); //坐标都是相对于手机屏幕 private int topY;//悬浮框顶部坐标 private int middleY;//悬浮框中间坐标 private int bottomY;//悬浮框底部坐标 private int selectPosition;//滚动时adapter当前选中的position private boolean fromTouch;//当调用smoothScrollToPositionFromTop()方法时也会触发onScroll,需要屏蔽掉 private WheelAdapter wheelAdapter;//数据适配器 private OnSelectCallback callback;//滚动时的回调接口 private int mMaxYOverScrollDistance; public interface OnSelectCallback { void onHandleScroll(int selectPosition); void onHandleIdle(WheelListView wheelListView, int selectPosition); } public WheelListView(Context context) { this(context, null); } public WheelListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public WheelListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);// initBounceListView(); setClipChildren(false); setClipToPadding(false); } /** * 阻尼效果实现 */// private void initBounceListView(){// //get the density of the screen and do some maths with it on the max overscroll distance// //variable so that you get similar behaviors no matter what the screen size// final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();// final float density = metrics.density;// mMaxYOverScrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);// }//// @Override// protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent){// //This is where the magic happens, we have replaced the incoming maxOverScrollY with our own custom variable mMaxYOverScrollDistance;// return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverScrollDistance, isTouchEvent);// } public void setAdapter(WheelAdapter adapter) { super.setAdapter(adapter); wheelAdapter = adapter; setCallback(adapter); } private void setCallback(OnSelectCallback callback) { this.callback = callback; } /** * 初始化 * * @param selectView * @param rootView */ protected void setUp(View selectView, View rootView) { if (null == selectView || null == rootView) { return; } //让listview现实的区域刚好和悬浮框重合 setPadding(0, selectView.getTop() , 0, rootView.getBottom() - selectView.getBottom() ); int location1[] = new int[2]; selectView.getLocationOnScreen(location1); topY = location1[1]; middleY = topY + selectView.getMeasuredHeight() / 2; bottomY = topY + selectView.getMeasuredHeight(); setUpScroll(); } /** * 设置滚动监听 */ private void setUpScroll() { setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (null == callback) { return; } if (scrollState == SCROLL_STATE_IDLE) { if (!fromTouch) { return; } fromTouch = false; SLog.d("SCROLL_STATE_IDLE"); //adapter实现了callback接口 callback.onHandleIdle(WheelListView.this,selectPosition); } else if (scrollState == SCROLL_STATE_TOUCH_SCROLL) { fromTouch = true; } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (null == callback) { return; } if (!fromTouch) { return; } synchronized (SCROLL_LOCK) { handleScroll(firstVisibleItem); } } }); } /** * 处理滚动 * * @param firstVisibleItem */ private void handleScroll(int firstVisibleItem) { //取出与中线距离最近的item int tempD = -1; //本次滑动计算出的position int tempP = -1; //遍历listview当前可见的item,不是所有item for (int i = 0; i < getChildCount(); i++) { //计算每个item相对于屏幕的坐标值 View child = getChildAt(i); int location2[] = new int[2]; child.getLocationOnScreen(location2); int childBottom = location2[1] + child.getMeasuredHeight(); int childTop = location2[1]; // SLog.d("bottomY : " + bottomY + " topY : " + topY + " middleY : " + middleY + " childBottom : " + childBottom + " childTop : " + childTop); //在悬浮框区域外的,排除掉 if (childBottom < topY || childTop > bottomY) { continue; } //找到item中线离悬浮框中线最近的item,比距离即可 int childMiddleY = childBottom - child.getMeasuredHeight() / 2; int position = firstVisibleItem + i;//当前item真正的position int distance = Math.abs(middleY - childMiddleY); if (tempD == -1) { tempD = distance; tempP = position; } else if (tempD > distance) { tempD = distance; tempP = position; } } if (tempP < 0) { tempP = 0; } else if (tempP > wheelAdapter.getCount() - 1) { tempP = wheelAdapter.getCount() - 1; } //防止多次notify同一个position if (selectPosition == tempP) { return; } selectPosition = tempP; callback.onHandleScroll(selectPosition); }}
再看看WheelAdapter
package com.ykbjson.demo.customview.listview;import android.content.Context;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import com.drivingassisstantHouse.library.base.SimpleAdapterHolder;import java.util.List;/** * 包名:com.ykbjson.demo.customview.listview * 描述:类似WheelView的ListView的adapter * 创建者:yankebin * 日期:2016/6/1 */public abstract class WheelAdapter<T> extends BaseAdapter implements WheelListView.OnSelectCallback { /** * 数据源 */ private List<T> mData; /** * 上下文 */ private Context mContext; /** * item布局索引 */ private int layoutId; public Context getmContext() { return mContext; } public void setmContext(Context mContext) { this.mContext = mContext; } public List<T> getmData() { return mData; } public void setmData(List<T> data) { this.mData = data; } public int getLayoutId() { return layoutId; } public void setLayoutId(int layoutId) { this.layoutId = layoutId; } /** * @param context 上下文 * @param data 数据源 * @param id item的布局资源文件 */ public WheelAdapter(Context context, List<T> data, int id) { this.mContext = context; this.mData = data; this.layoutId = id; } /** * 数据源改变,刷新界面 * * @param data */ public void refersh(List<T> data) { this.mData = data; notifyDataSetChanged(); } @Override public int getCount() { if (mData != null) { return mData.size(); } return 0; } @Override public T getItem(int position) { if (mData != null) { return mData.get(position); } return null; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { SimpleAdapterHolder holder = SimpleAdapterHolder.get(convertView, parent, layoutId, position); covertView(holder, position, mData, getItem(position)); return holder.getmConvertView(); } /** * 子类可重写此方法实现不同的滚动效果 * @param wheelListView * @param selectPosition */ @Override public void onHandleIdle(WheelListView wheelListView, int selectPosition) { notifyDataSetChanged(); //精确滚动到某个item的方法,其他的请看api wheelListView.smoothScrollToPositionFromTop(selectPosition, 0, 400); } public abstract void covertView(SimpleAdapterHolder holder, int position, List<T> dataSource, T data);}
SimpleAdapterHolder其实就是一般的viewHolder
import android.util.SparseArray;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;/** * 包名:com.ykbjson.demo.customview.listview * 描述:通用viewholder * 创建者:yankebin * 日期:2016/6/1 */public class SimpleAdapterHolder { @SuppressWarnings("unused") private int mPosition; private View mConvertView; SparseArray<View> mMembers; public View getmConvertView() { return mConvertView; } public SimpleAdapterHolder() { } private SimpleAdapterHolder(ViewGroup parent, int layoutId, int position) { this.mPosition = position; this.mMembers = new SparseArray<View>(); mConvertView = LayoutInflater.from(parent.getContext()).inflate( layoutId, parent, false); mConvertView.setTag(this); } public static SimpleAdapterHolder get(View convertView, ViewGroup parent, int layoutId, int position) { if (convertView == null) { return new SimpleAdapterHolder(parent, layoutId, position); } else { return (SimpleAdapterHolder) convertView.getTag(); } } @SuppressWarnings("unchecked") public <T extends View> T getView(int viewId) { View view = mMembers.get(viewId); if (view == null) { view = mConvertView.findViewById(viewId); mMembers.put(viewId, view); } return (T) view; }}
为了使用方便,我在外面包装了一层WheelView
package com.ykbjson.demo.customview.listview;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Color;import android.util.AttributeSet;import android.view.Gravity;import android.view.LayoutInflater;import android.view.View;import android.widget.FrameLayout;import android.widget.ImageView;import com.ykbjson.demo.R;import com.ykbjson.demo.tools.SLog;/** * 包名:com.ykbjson.demo.customview.otherview * 描述:滚轮视图 * 创建者:yankebin * 日期:2016/6/2 */public class WheelView extends FrameLayout { private WheelListView wheelListView; private View mSelectView; public WheelView(Context context) { this(context, null); } public WheelView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public WheelView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } /** * 初始化视图 * * @param context * @param attrs */ private void initView(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WheelView); int floatId = typedArray.getResourceId(R.styleable.WheelView_float_layout, -1); int bgId = typedArray.getResourceId(R.styleable.WheelView_background_resources, -1); SLog.d("bgId : " + bgId); typedArray.recycle(); if (-1 == floatId) { throw new IllegalArgumentException("resId is invalid"); } //背景 ImageView imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.FIT_XY); try { imageView.setLayerType(LAYER_TYPE_SOFTWARE, null); } catch (Exception e) { e.printStackTrace(); } if (-1 != bgId) { imageView.setImageResource(bgId); } FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-1, -1); addView(imageView, params); //listview wheelListView = new WheelListView(context); wheelListView.setBackgroundColor(Color.TRANSPARENT); wheelListView.setClipToPadding(false); wheelListView.setClipChildren(false); params = new FrameLayout.LayoutParams(-1, -1); params.gravity = Gravity.CENTER; addView(wheelListView, params); //悬浮视图 mSelectView = LayoutInflater.from(context).inflate(floatId, this, false); params = new FrameLayout.LayoutParams(-1, -2); params.gravity = Gravity.CENTER; addView(mSelectView, params); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); wheelListView.setUp(mSelectView, this); } public void setAdapter(WheelAdapter adapter) { wheelListView.setAdapter(adapter); } public WheelListView getWheelListView() { return wheelListView; }}
定义的属性
<!-- 仿wheelview的listvie--> <declare-styleable name="WheelView"> <attr name="float_layout" format="reference" /> <attr name="background_resources" format="reference|color" /> </declare-styleable>
最后的使用
在xml里
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.ykbjson.demo.customview.listview.WheelView app:float_layout="@layout/layout_scroll_list" app:background_resources="@drawable/trip_bg" android:id="@+id/wheel" android:layout_width="match_parent" android:layout_height="match_parent"/></FrameLayout>
在代码里
package com.ykbjson.demo.activity;import android.content.Context;import android.os.Bundle;import android.os.Message;import android.view.View;import android.widget.TextView;import com.drivingassisstantHouse.library.base.BaseActivity;import com.drivingassisstantHouse.library.base.SimpleAdapterHolder;import com.nineoldandroids.view.ViewHelper;import com.nostra13.universalimageloader.core.ImageLoader;import com.ykbjson.demo.R;import com.ykbjson.demo.bean.AssistantManager;import com.ykbjson.demo.customview.listview.WheelAdapter;import com.ykbjson.demo.customview.listview.WheelView;import com.ykbjson.demo.customview.otherview.CircleImageView;import java.util.ArrayList;import java.util.List;import butterknife.Bind;/** * 包名:com.ykbjson.demo.activity * 描述: * 创建者:yankebin * 日期:2016/5/25 */public class TestTravelListActivity extends BaseActivity { @Bind(R.id.wheel) WheelView wheel; private ArrayList<AssistantManager> assistantManagers = new ArrayList<>(); private TravelListAdapter adapter; private void dataChange() { adapter = new TravelListAdapter(this, assistantManagers, R.layout.item_assistant_avatar); wheel.setAdapter(adapter); } @Override public int bindLayout() { return R.layout.activity_scroll_list; } @Override public void initParms(Bundle parms) { } @Override public void initView(View view) { for (int i = 0; i < 2; i++) { AssistantManager manager = new AssistantManager(); manager.setMobile("15208279347"); manager.setName("客服" + i); manager.setNickName("简途客户" + i); manager.setAssistantId(i); manager.setAvatar("drawable://" + R.drawable.customer_service_head); manager.setPhoto("drawable://" + R.drawable.ad_page); manager.setDescription("呵呵呵呵 的期望的开启电脑去"); manager.setType(i % 2 == 0 ? 0 : 1); manager.setTourismSections("成都-都江堰-青城山"); assistantManagers.add(manager); } } @Override public void doBusiness(Context mContext) { baseHandler.sendEmptyMessageDelayed(1, 200); } @Override public void resume() { } @Override public void destroy() { } @Override public void handleMessage(Message msg) { dataChange(); } /*主要代码**/ private class TravelListAdapter extends WheelAdapter<AssistantManager> { private int mSelectPosition; /** * @param context 上下文 * @param data 数据源 * @param id item的布局资源文件 */ public TravelListAdapter(Context context, List data, int id) { super(context, data, id); } @Override public void onHandleScroll(int selectPosition) { mSelectPosition = selectPosition; notifyDataSetChanged(); } @Override public void covertView(SimpleAdapterHolder holder, int position, List<AssistantManager> dataSource, AssistantManager manager) { float scale = 1f; if (mSelectPosition == position) { scale = 1.2f; } TextView tvName = holder.getView(R.id.tv_name); tvName.setText(manager.getName()); CircleImageView imageView = holder.getView(R.id.iv_avatar); ImageLoader.getInstance().displayImage(manager.getAvatar(), imageView); ViewHelper.setScaleX(holder.getmConvertView().findViewById(R.id.layout_content), scale); ViewHelper.setScaleY(holder.getmConvertView().findViewById(R.id.layout_content), scale); } }}
- ListView实现类似WheelView效果的探究
- Android使用wheelView实现简单类似ios PickerView选择器效果
- 如何实现类似水平WheelView的自定义滑动控件
- ListView 实现类似listview + scrollview滚动效果
- 一个类似Listview的效果
- 一个类似Listview的效果
- ListView 实现类似电话薄标题碰撞效果
- 基于VerticalViewPager的上下滑动,可带动画效果,类似WheelView
- listview 弹窗效果实现 +类似qq的滑动删除效果
- 与圆角listview类似的效果
- 类似NumberPicker的效果实现
- 类似horizon效果的实现
- 自定义WheelView UI 实现Ios滚轮效果
- 实现类似Instagram、qq中ListView每一项标题置顶的效果
- 仿StickyListHeaders 实现listview的header滑动效果(类似QQ好友列表)
- wheelview--滚动效果的日期选择器View
- Android-ListView实现类似Gallery的功能
- Android-ListView实现类似Gallery的功能
- 安装配置tomcat的步骤
- 什么是oAuth
- java面向对象思想1
- spring基础学习一 : 搭建spring基本架构
- 对于一个平庸java程序员的自述
- ListView实现类似WheelView效果的探究
- CSDN的第一篇博客
- php笔记3-常量
- linux(centos6.6) 下安装,配置redis, 及开机自启动
- 【数据结构】HashSet原理及实现学习总结
- lucene---QueryParser用法示例
- 初学Maven所报的各种错
- 动态从excel取出数据,并封装动态javabean存入数据库
- Spring MVC 的请求参数获取的几种方法