多重ScrollView、HorizontalScrollView、ListView嵌套的死磕!

来源:互联网 发布:python编程思想 编辑:程序博客网 时间:2024/06/07 02:29

写在前面的话:
本来计划第一篇自己的博客是计划从CustomView开始的,由于个人的原因呢。一直搁置了两个月下来,这段时间发生的事情,过程不算顺利,但是收获不可谓不大!本例呢是根据公司最近的需求做的demo,原创度不高,更多的是收集整合及问题的解决方法,但是这么复杂的嵌套真真是入坑出坑,那心酸!更心酸的是UI出效果图后跟需求没有一个是一样的!效果全变了,需求也相应的变更了,让自己的死磕变得毫无意义,毫无意义……!不过好的一点呢,这反而不是公司的东西了,反而没了后顾之忧!
大概的效果呢有点类似汽车之家汽车配置界面的表单,粗看其实没什么,细看后,一直没想出个解决办法来,本想着用纯CustomView的方式来完成。一开始就觉得采用组合形式的来实现觉得太low(然而然而被打脸了Crying),大至写了个demo越来越觉得不靠谱,实现的东西太多了(主要还是能力有限哈^^)。

汽车之家效果

然后无意中看到京东上列表联动的效果,可以从这方面入手,回来一搜还真有类似的实现(文末会贴出^^),解决办法呢其实就是两个ScrollView嵌套然后产生一个联动的效果。
这里主要是讲遇到的坑哈,前期需求并没有确定,那自己就只有全面的更全面的去考虑,预防备用着。本例一个是为记录下个人的过程,也是为了方便碰到类似问题的同学少走弯路吧,好了^^,闲言碎语就到这里吧,开始那百分之二十!

这个是本demo的效果,
这是本demo的效果

  • 这是整个xml嵌套的结构!疯了吧^^

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……,哈哈,当时心情瞬间就释然了!就是见不得别人比我惨,哈哈哈^^。

我是源码点我

0 0
原创粉丝点击