多重ScrollView、HorizontalScrollView、ListView嵌套的死磕!
来源:互联网 发布:python编程思想 编辑:程序博客网 时间:2024/06/07 02:29
写在前面的话:
本来计划第一篇自己的博客是计划从CustomView开始的,由于个人的原因呢。一直搁置了两个月下来,这段时间发生的事情,过程不算顺利,但是收获不可谓不大!本例呢是根据公司最近的需求做的demo,原创度不高,更多的是收集整合及问题的解决方法,但是这么复杂的嵌套真真是入坑出坑,那心酸!更心酸的是UI出效果图后跟需求没有一个是一样的!效果全变了,需求也相应的变更了,让自己的死磕变得毫无意义,毫无意义……!不过好的一点呢,这反而不是公司的东西了,反而没了后顾之忧!
大概的效果呢有点类似汽车之家汽车配置界面的表单,粗看其实没什么,细看后,一直没想出个解决办法来,本想着用纯CustomView的方式来完成。一开始就觉得采用组合形式的来实现觉得太low(然而然而被打脸了Crying),大至写了个demo越来越觉得不靠谱,实现的东西太多了(主要还是能力有限哈^^)。
然后无意中看到京东上列表联动的效果,可以从这方面入手,回来一搜还真有类似的实现(文末会贴出^^),解决办法呢其实就是两个ScrollView嵌套然后产生一个联动的效果。
这里主要是讲遇到的坑哈,前期需求并没有确定,那自己就只有全面的更全面的去考虑,预防备用着。本例一个是为记录下个人的过程,也是为了方便碰到类似问题的同学少走弯路吧,好了^^,闲言碎语就到这里吧,开始那百分之二十!
这个是本demo的效果,
- 这是整个xml嵌套的结构!疯了吧^^
- 核心代码块
这里采用的是继承自LinearLayout 通过xml引用的方式作组合产生一个控件,中间包含时间轴的绘制及时间段常用置前,和用于时间标尺作用的TimeBar 当然这不是文章的重点请选择性忽略^^
public class AsyncMeetingView extends LinearLayout{ private int timeModel=24; private int timeStart=7; /**item 宽高 最终于工具类转换*/ private int itemWidth= (int) getResources().getDimension(R.dimen.activity_meeting_times_titelWidth); private int ItemHeight= (int) getResources().getDimension(R.dimen.activity_meeting_times_titelHeight); private AsyncHorizontalScrollView shs_titel; private AsyncHorizontalScrollView shs_rightcontent; private ListView lv_left_name; private ListView lv_rightcontent; private LinearLayout ll_rightitle; private Button btn_meetingType; private View view_timeBar; private BaseAdapter LeftNameAdapter,RightContentAdapter; private Context context; private View parentView=null; private Handler handler=new Handler(); private String[] times; private ScrollView scrollView_parent; private AsyncScrollView innerScrollView_content; public AsyncMeetingView(Context context) { this(context,null); } public AsyncMeetingView(Context context, AttributeSet attrs) { super(context, attrs); this.context=context; parentView=LayoutInflater.from(context).inflate(R.layout.activity_main,this); initView(); } private void initView() { initTitelTimesItem(); btn_meetingType= (Button) findViewById(R.id.btn_meetingType); view_timeBar = (View) findViewById(R.id.view_timeBar); lv_rightcontent = (ListView)findViewById(R.id.lv_rightcontent); lv_rightcontent.setFocusable(false); shs_rightcontent = (AsyncHorizontalScrollView)findViewById(R.id.shs_rightcontent); shs_rightcontent.setFocusable(false);//关键关掉的 lv_left_name = (ListView)findViewById(R.id.lv_left_name); lv_left_name.setFocusable(false); shs_titel = (AsyncHorizontalScrollView)findViewById(R.id.shs_titel);// 嵌套问题 scrollView_parent= (ScrollView) findViewById(R.id.scrollView_parent); innerScrollView_content=(AsyncScrollView) findViewById(R.id.innerScrollView_content); innerScrollView_content.setParentScrollView(scrollView_parent); innerScrollView_content.setBtn_meetingType(btn_meetingType); /**解决不置顶的问题*/ scrollView_parent.setFocusable(true); scrollView_parent.setFocusableInTouchMode(true); scrollView_parent.requestFocus(); } public void setTimeBarParams(int widht,int color){ view_timeBar.setLayoutParams(new FrameLayout.LayoutParams(widht, LayoutParams.MATCH_PARENT)); view_timeBar.setBackgroundColor(color); } public void setTimeBarTranslationX(final int location){ view_timeBar.setTranslationX(location);// 退一格,相对居中显示 int scrollLocation=location-(int)(getResources().getDimension(R.dimen.activity_meeting_times_titelWidth)); shs_rightcontent.smoothScrollTo(scrollLocation,0); } /**实例时间段*/ private void initTitelTimesItem() { ll_rightitle= (LinearLayout)findViewById(R.id.ll_rightitle); times=transFormTime(timeModel,timeStart); for (int i=0;i<times.length;i++){ AsyncMeetingTextView tv_time=new AsyncMeetingTextView(context); tv_time.setLayoutParams( new LinearLayout.LayoutParams(itemWidth,ItemHeight)); tv_time.setText(times[i]); tv_time.setGravity(Gravity.CENTER); tv_time.setTextSize(getResources().getDimension(R.dimen.activity_meeting_times_textSize)); ll_rightitle.addView(tv_time); Log.v("times====",times[i]); } } /** * 根据列数生成相应的时间数组 以8:00/startPoint 起始点 开头 * 以第二列开始,第一列为标注:时间/人员 */ public String[] transFormTime(int column,int startPoint) {//24,7 String[] times = new String[column]; for (int i = 0; i < times.length; i++) { if (i >= startPoint && i < times.length) {// =7 && <24 if (i != 24) { times[i-startPoint] = i + ":00"; } else { times[i-startPoint] = "00:00";//24:00转成 00:00 } }else if(i>=0 && i<startPoint){// times[times.length - (startPoint- i)] = i + ":00";//(index 19-24) } } return times; } public void setTimeModel(int timeModel) { this.timeModel = timeModel; } public void setTimeStart(int timeStart) { this.timeStart = timeStart; } public int getItemWidth() { return itemWidth; } public void setItemWidth(int itemWidth) { this.itemWidth = itemWidth; } public int getItemHeight() { return ItemHeight; } public void setItemHeight(int itemHeight) { ItemHeight = itemHeight; } public void setView_timeBar(View view_timeBar) { this.view_timeBar = view_timeBar; } public Button getBtn_meetingType() { return btn_meetingType; } public String[] getTimes() { return times; } public void setLeftNameAdapter(BaseAdapter leftNameAdapter) { LeftNameAdapter = leftNameAdapter; if (LeftNameAdapter!=null) lv_left_name.setAdapter(LeftNameAdapter); shs_titel.setmView(shs_rightcontent); UtilTools.setListViewHeightBasedOnChildren(lv_left_name); } public void setRightContentAdapter(BaseAdapter rightContentAdapter) { RightContentAdapter = rightContentAdapter; if (RightContentAdapter!=null) lv_rightcontent.setAdapter(RightContentAdapter); shs_rightcontent.setmView(shs_titel); UtilTools.setListViewHeightBasedOnChildren(lv_rightcontent); }}
这里大概分为五大问题进行说明
- 问题一:关于联动分析
这里主要是解决两个HorizontalScrollView横向滑动联动的效果(时间头跟内容体),自定义AsyncHorizontalScrollView继承自HorizontalScrollView。通过对其它HorizontalScrollView的引用,在onScrollChanged方法中进行scrollTo(x,y)设置,达到两个HorizontalScrollView绑定的效果,从面产生联动的效果
public class AsyncHorizontalScrollView extends HorizontalScrollView { private View mView; public AsyncHorizontalScrollView(Context context) { super(context); } public AsyncHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public AsyncHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (mView!=null) mView.scrollTo(l,t);// Log.v("滑动值:",l+"===="+t); } public void setmView(View mViwe) { this.mView = mViwe; }}
- 问题点二,三:嵌套在AsyncHorizontalScrollView中的ListView(也就是内容体中的ListView)重新测量的问题。
由于ScrollView中嵌套ListView会产生只显示一条数据,这是因为adapter内容是后加载的,这就要手动的去根据item数量去重新测量整个ListView的高度,这里其实也带出了问题点三:也就是HorizontalScrollView宽度大于ListView宽度的问题,从面产生空白区域的问题,这里一并说了。这个工具类也是直接参考网上的,只是作了宽度的设置。
public class UtilTools { static int totalWidth=0; public static void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; int len = listAdapter.getCount(); for (int i = 0; i < len; i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); totalWidth=listItem.getMeasuredWidth(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); params.width=totalWidth;//还是算出宽度,解决多出白边的问题 listView.setLayoutParams(params); }}
- 问题点四:整个页面再次嵌套在一个主竖直ScrollView中
由于竖直方向跟横向方向都要做相应的滑动,这里就会产生事件冲突的问题,这里主要还是采用能过内部拦截的方式去实现,那么子ScorllView就要从新自定义了,这里由于考虑了实际的需求多了两种情况的逻辑处理:- 一是已到达过底部,再滑动子控件,此时先转到父控件先显示出时间轴再滑动子控件
- 二是子控件滑动到中间,这时切换到父控件滑动,此时时间轴不可见了,这时再滑动子控件时 判断时间轴是否可见 *
public class AsyncScrollView extends ScrollView { /** * 外层ScrollView */ private ScrollView parentScrollView; /** * 外层的头部时间控件,用于判断时间轴是否正屏幕内可见 * */ private Button btn_meetingType; private int mTop = 10; private int lastScrollDelta = 0; private int currentY; /**判断时间轴是否在屏幕内显示*/ private boolean isTimeItemShow=true; /**判断子控件是否滑动到底部*/ private boolean isConvertDownGetParent=false; public AsyncScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public void resume() { overScrollBy(0, -lastScrollDelta, 0, getScrollY(), 0, getScrollRange(), 0, 0, true); lastScrollDelta = 0; } /** * 将targetView滚到最顶端 */ public void scrollToTop(View targetView) { int oldScrollY = getScrollY(); int top = targetView.getTop() - mTop; int delatY = top - oldScrollY; lastScrollDelta = delatY; overScrollBy(0, delatY, 0, getScrollY(), 0, getScrollRange(), 0, 0, true); } private int getScrollRange() { int scrollRange = 0; if (getChildCount() > 0) { View child = getChildAt(0); scrollRange = Math.max(0, child.getHeight() - (getHeight())); } return scrollRange; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (parentScrollView == null) { return super.onInterceptTouchEvent(ev); } else { if (ev.getAction() == MotionEvent.ACTION_DOWN) { // 将父scrollview的滚动事件拦截 currentY = (int)ev.getY(); setParentScrollAble(false); return super.onInterceptTouchEvent(ev); } else if (ev.getAction() == MotionEvent.ACTION_UP) { // 把滚动事件恢复给父Scrollview setParentScrollAble(true); Log.v("onInterceptTouchEventUp","把滚动事件恢复给父Scrollview"); } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { DisplayMetrics dm=getResources().getDisplayMetrics(); int[] ints=new int[]{dm.widthPixels,dm.heightPixels};// Log.v("onInterceptTouchEventMoveBtn",btn_meetingType.getLocationOnScreen(ints)); isTimeItemShow=btn_meetingType.getLocalVisibleRect(new Rect(0,0,dm.widthPixels,dm.heightPixels)); Log.v("onInterceptTouchEventMove","把滚动事件恢复给父Scrollview:"+isTimeItemShow); } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { View child = getChildAt(0); if (parentScrollView != null) { if (ev.getAction() == MotionEvent.ACTION_MOVE) { int height = child.getMeasuredHeight(); height = height - getMeasuredHeight(); Log.d("innr_onTouchEvent","height=" + height); int scrollY = getScrollY(); Log.d("innr_onTouchEvent","_scrollY=" + scrollY); int y = (int)ev.getY(); // 手指向下滑动 if (currentY < y) { if (scrollY <= 0) { // 如果向下滑动到头,就把滚动交给父Scrollview setParentScrollAble(true); return false; } else { /**两种情况向下滑动是,要由子控件先切到父控件显示出时间后再内部滑动, * 一是已到达过底部,再滑动子控件,此时先转到父控件先显示出时间轴再滑动子控件 * 二是子控件滑动到中间,这时切换到父控件滑动,此时时间轴不可见了,这是再滑动子控件时 判断时间轴是否可见。。。*/ if ((isConvertDownGetParent==true |isTimeItemShow==false) && height-scrollY>40){ setParentScrollAble(true); isConvertDownGetParent=false; }else { setParentScrollAble(false); } } } else if (currentY > y) { if (scrollY >= height) {// 子控件已滑动到底部 isConvertDownGetParent=true; // 如果向上滑动到头,就把滚动交给父Scrollview setParentScrollAble(true); return false; } else { setParentScrollAble(false); } } currentY = y; } } return super.onTouchEvent(ev); } /** * 是否把滚动事件交给父scrollview * * @param flag */ private void setParentScrollAble(boolean flag) { parentScrollView.requestDisallowInterceptTouchEvent(!flag); } public void setParentScrollView(ScrollView parentScrollView) { this.parentScrollView = parentScrollView; } public void setBtn_meetingType(Button btn_meetingType) { this.btn_meetingType = btn_meetingType; }}
- 问题点五:多重嵌套时产生的ScrollView中内容不置顶,反而ListView反而会置顶的问题
这里有的文章中说到通过线程调用ScorllView中的smoothScrollTo(x,y)方法滑动到顶部的方法,单层嵌套这样处理是可以的,但是…本例嵌套过多,内部的子ListView还是会有这种问题,所以本例不适用。总结了下大至通过以下三个步骤实测可解决(不同的嵌套方式要自行调整哦)
一、在代码里去掉listview的焦点如
lv_rightcontent = (ListView)findViewById(R.id.lv_rightcontent);lv_rightcontent.setFocusable(false);lv_left_name = (ListView)findViewById(R.id.lv_left_name);lv_left_name.setFocusable(false);
二、在Listview外套一层LinearLayout,如果是其它GroupView获取焦点
<com.demo.jiangyuehua.myhscorllrelatelistview.view.AsyncHorizontalScrollViewandroid:id="@+id/shs_rightcontent"android:layout_width="match_parent"android:layout_height="match_parent" android:focusableInTouchMode="true"android:focusable="true" >或<LinearLayoutandroid:layout_width="match_parent" android:layout_height="wrap_content"android:focusable="true"android:focusableInTouchMode="true">
三、最后就是要在你的主ScrollView中通过代码添加获得焦点方法
scrollView_parent.setFocusable(true);scrollView_parent.setFocusableInTouchMode(true);scrollView_parent.requestFocus();
相关文章参考引用 Thanks ^^:
http://blog.csdn.net/elinavampire/article/details/42142551
http://blog.csdn.net/jiaoyaning1210/article/details/51084246
基本核心的就是这些了,前前后后,不停的入坑出坑,过程不可谓不波折,占用了你的百分之二十以及百分之三十甚至百分之四十,但是每一次问题的解决都是值得欣慰的,enjoy!
这里还是要说下小插曲,评审时当看到UI效果图跟需求原型没有一毛钱关系时,当时就懵逼了,两个星期的准备就这么白费了,当时那个心情呐。本身当天由于个人的事情一点状态也没有,不停的在挣扎、调整中,还来这么一出。哎……,可当看到iOS的哥们直接就暴走!然后balabala……,哈哈,当时心情瞬间就释然了!就是见不得别人比我惨,哈哈哈^^。
我是源码点我
- 多重ScrollView、HorizontalScrollView、ListView嵌套的死磕!
- 有弹性的ScrollView&ListView&HorizontalScrollView
- 有弹性的ScrollView&ListView&HorizontalScrollView
- 有弹性的ScrollView&ListView&HorizontalScrollView
- scrollview嵌套HorizontalScrollView
- ListView,ScrollView,RecyclerView被HorizontalScrollView嵌套,加载图片
- 有弹性的ScrollView&ListView&HorizontalScrollView
- listview 中嵌套 HorizontalScrollView 出现的问题
- scrollview嵌套listview 嵌套HorizontalScrollView嵌套gridview 焦点及卡顿问题
- ScrollView嵌套ListView的解决方案
- ScrollView嵌套ListView的使用
- ScrollView嵌套ListView的解决办法
- ScrollView嵌套ListView的解决方案
- scrollView嵌套listview的问题
- ScrollView嵌套ListView的问题,
- ScrollView嵌套ListView的解决方案
- ScrollView和ListView的嵌套
- ScrollView嵌套listview的问题
- DLL导出类
- Java网络通信之HttpUrlConnection
- 部分和问题
- 8.Linux系统的目录结构
- java中方法的参数传递的是什么?
- 多重ScrollView、HorizontalScrollView、ListView嵌套的死磕!
- android中内存调试信息的解读
- C++四本有用书
- 【错误解析】关于ORA-4025错误
- 取得数组中的随机数random.nextInt(index)
- 【Python自动化】编程控制类型变量顺序化
- Centos下新建用户及修改用户目录
- 编写更好的CSS代码
- 写java时候,可能会经常遇到异常,如果对异常有个清晰的认识,异常并不可怕