之前的一遍学习笔记主要就Android滑动冲突中,在不同方向的滑动所造成冲突进行了了解,这种冲突很容易理解,当然也很容易解决。今天,就同方向的滑动所造成的冲突进行一下了解,这里就先以垂直方向的滑动冲突为背景,这也是日常开发中最常见的一种情况。

这里先看一张效果图

由于GIF 图片大小的限制,截图效果不是很好

上图是在购物软件上常见的上拉查看图文详情,关于这中动画效果的实现,其实实现整体的效果,办法是有很多的,网上有很多相关的例子,但是对某些细节的处理不是很清晰,比如,下拉之后显示的部分(例如底部的图文详情)又是一个类ScrollView的控件(比如WebView)的话,又会产生新的问题。这里就以下拉查看图文详情为背景做一下同方向滑动冲突的分析。

整体思路

这里看下图

首先,关于这张图做一些设定:

  • 黑色的框代表手机屏幕
  • 绿色的框代表一个外层的ScrollView
  • 两个红色的框代表嵌套在里面的两个类ScrollView控件1,这里我们就暂时简称为 SUp,SDown

好了,接下来就分析一下实现整个流程的过程。

这里必须明确的一点,无论何时,SUp和SDown可见的部分始终是手机屏幕的高度。知道了这一点,我们就可以按以下步骤展开

首先,我们确保外部的ScrollView不拦截滑动事件,这样SUp必然获得此次事件,然后根据其Action_Move事件,当其为向下滑动自身滑动距离+屏幕高度=其自身高度 时,即可认为SUp滑动到了底部,此时外部ScrollView可拦截滑动事件,从而保证能够继续向下滑动,这个时候底部SDown就显示出来了。

同理,这时候外部ScrollView不允许外部ScrollView拦截滑动事件,由SDown处理,根据其Action_move事件,当其为向上滑动,且自身可滑动距离为0时,就说明SDown已经滑动到了顶部,这时外部ScrollView又可以获得拦截滑动事件的权利,从而保证整个视图能够向上继续滑动,此时SUp再次显示,有开始新一轮循环拦截。

这样整体的一个流程就可以实现动图中的效果。好了,说完原理,还是看代码。

代码实现

SUp实现

public class UpScrollView extends ScrollView {    /**     * 屏幕高度     */    private int mScreenHeight;    /**     * 上一次的坐标     */    private float mLastY;    /**     * 当前View滑动距离     */    private int mScrollY;    /**     * 当前View内子控件高度     */    private int mChildH;    public UpScrollView(Context context) {        super(context);        init(context);    }    public UpScrollView(Context context, AttributeSet attrs) {        super(context, attrs);        init(context);    }    public UpScrollView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context);    }    private void init(Context context) {        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);        DisplayMetrics dm = new DisplayMetrics();        wm.getDefaultDisplay().getMetrics(dm);        mScreenHeight = dm.heightPixels;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        //默认设定顶层View不拦截        getParent().getParent().requestDisallowInterceptTouchEvent(true);        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastY = (int) ev.getY();                break;            case MotionEvent.ACTION_MOVE:                float y = ev.getY();                float deltaY = y - mLastY;                mChildH = this.getChildAt(0).getMeasuredHeight();                int translateY = mChildH - mScrollY;                //向上滑动时,如果translateY等于屏幕高度时,即表明滑动到底部,可又顶层View控制滑动                if (deltaY < 0 && translateY == mScreenHeight) {                    getParent().getParent().requestDisallowInterceptTouchEvent(false);                }                break;            default:                break;        }        return super.onTouchEvent(ev);    }    @Override    protected void onScrollChanged(int l, int t, int oldl, int oldt) {        super.onScrollChanged(l, t, oldl, oldt);        mScrollY = t;    }}

这里在ACTION_MOVE里做了减法,其实道理是一样的。

onScrollChanged 是在View类中实现,查看其API可以看到其第二个参数t解释

  • @param t Current vertical scroll origin.

即为当前View此次滑动的距离

SDown实现

public class MyWebView extends WebView {    public float oldY;    private int t;    public MyWebView(Context context) {        super(context);        init();    }    public MyWebView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init() {        WebSettings settings = getSettings();        settings.setJavaScriptEnabled(true);        setWebViewClient(new WebViewClient() {            @Override            public boolean shouldOverrideUrlLoading(WebView view, String url) {                view.loadUrl(url);                return true;            }        });    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        getParent().getParent().requestDisallowInterceptTouchEvent(true);        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                oldY = ev.getY();                break;            case MotionEvent.ACTION_MOVE:                float Y = ev.getY();                float Ys = Y - oldY;                if (Ys > 0 && t == 0) {                    getParent().getParent().requestDisallowInterceptTouchEvent(false);                }                break;        }        return super.onTouchEvent(ev);    }    @Override    protected void onScrollChanged(int l, int t, int oldl, int oldt) {        this.t = t;        super.onScrollChanged(l, t, oldl, oldt);    }}

看以看到,这里底部的View并没有继承ScrollView,而是选择继承了WebView,这里只是为了方便,当然继承ScrollView也是没有问题。这里只是需要按实际情况考虑,因为底部图文详情的内容就是一个WebView加载数据。

这个类的实现,按照之前说的原理应该很好理解。

外部ScrollView

public class CustomerScrollViews extends ScrollView {    /**     * 屏幕高度     */    private int mScreenHeight;    private UpScrollView upScrollView;    private MyWebView myWebView;    private boolean init = false;    private float fator = 0.2f;    private int factorHeight;    private boolean upShow = true;    public CustomerScrollViews(Context context) {        super(context);        init(context);    }    public CustomerScrollViews(Context context, AttributeSet attrs) {        super(context, attrs);        init(context);    }    public CustomerScrollViews(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context);    }    private void init(Context context) {        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);        DisplayMetrics dm = new DisplayMetrics();        wm.getDefaultDisplay().getMetrics(dm);        mScreenHeight = dm.heightPixels;        factorHeight = (int) (mScreenHeight * fator);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        if (!init) {            LinearLayout parentView = (LinearLayout) getChildAt(0);            //获得内部的两个子view            upScrollView = (UpScrollView) parentView.getChildAt(0);            myWebView = (MyWebView) parentView.getChildAt(2);//            //并设定其高度为屏幕高度            upScrollView.getLayoutParams().height = mScreenHeight;            myWebView.getLayoutParams().height = mScreenHeight;            init = true;        }        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);        if (changed) {            scrollTo(0, 0);        }    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_UP:                int scrollY = getScrollY();                if (upShow) {                    if (scrollY <= factorHeight) {                        smoothScrollTo(0, 0);                    } else {                        smoothScrollTo(0, mScreenHeight);                        upShow = false;                    }                } else {                    int scrollpadding = mScreenHeight - scrollY;                    if (scrollpadding >= factorHeight) {                        this.smoothScrollTo(0, 0);                        upShow = true;                    } else {                        this.smoothScrollTo(0, mScreenHeight);                    }                }                return true;        }        return super.onTouchEvent(ev);    }}

这个类的实现,就很灵活了,在onMeasure方法中初始化完内部的View之后,在OnTouch方法中就可以根据实际需求完成不同的逻辑实现,这里只是为了仿照查看图文详情的效果,对整个视图通过ScrollView的smoothScrollTo方法进行位移变化,这个逻辑很简单。

这里重点说一下一个地方:

upScrollView = (UpScrollView) parentView.getChildAt(0);myWebView = (MyWebView) parentView.getChildAt(2);

你可能会奇怪中间的child(1)去了哪里?这里还要从MainActivity的布局文件说起

dual_scrollview_activity_layout1.xml

<?xml version="1.0" encoding="utf-8"?>"http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <com.example.dreamwork.activity.superscrollview.CustomerScrollViews        android:layout_width="match_parent"        android:layout_height="match_parent">        "match_parent"            android:layout_height="wrap_content"            android:orientation="vertical">            <com.example.dreamwork.activity.superscrollview.UpScrollView                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:scrollbars="vertical">                "match_parent"                    android:layout_height="wrap_content"                    android:orientation="vertical">                    "match_parent"                        android:layout_height="wrap_content"                        android:scaleType="fitXY"                        android:src="@drawable/taobao" />                    "match_parent"                        android:layout_height="wrap_content"                        android:scaleType="fitXY"                        android:src="@drawable/taobao" />                    "20sp"                        android:padding="10dp"                        android:gravity="center"                        android:layout_marginTop="20dp"                        android:layout_marginBottom="60dp"                        android:text="查看图文详情"                        android:layout_width="match_parent"                        android:layout_height="wrap_content" />                            com.example.dreamwork.activity.superscrollview.UpScrollView>            "@layout/selector_tab_items" />            <com.example.dreamwork.activity.superscrollview.MyWebView                android:id="@+id/web"                android:layout_width="match_parent"                android:layout_height="match_parent"/>            com.example.dreamwork.activity.superscrollview.CustomerScrollViews>

整个布局文件可以看出,我们在CustomerScrollViews这个最外层的自定义ScrollView内部又放置了两个自定义的ScrollView(就如我们看到的原理图那样),只不过在这两个ScrollView类控件的中间通过layout又放置一个LinearLayout,里面的内容就是在动图中看到的那个中间的写着qq,baidu字样的用于切换WebView内容的一个View。这里就不贴代码了。

这样,你就可以理解之前的child(1)为什么被跳过了吧。

使用

public class DualScrollViewActivity1 extends Activity implements View.OnClickListener {    private MyWebView webView;    private TextView sinaTv, qqTv, baiduTv;    private View line1, line2, line3;    private final String BAIDU = "http://www.baidu.com";    private final String QQ = "http://www.qq.com";    private final String SINA = "http://sina.cn";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        requestWindowFeature(Window.FEATURE_NO_TITLE);        InitView();        sinaTv.performClick();    }    private void InitView() {        setContentView(R.layout.dual_scrollview_activity_layout1);        webView = V.f(this, R.id.web);        sinaTv = V.f(this, R.id.one);        sinaTv.setOnClickListener(this);        qqTv = V.f(this, R.id.two);        qqTv.setOnClickListener(this);        baiduTv = V.f(this, R.id.three);        baiduTv.setOnClickListener(this);        line1 = V.f(this, R.id.line1);        line2 = V.f(this, R.id.line2);        line3 = V.f(this, R.id.line3);    }    @Override    public void onClick(View v) {        reset();        String url = "";        switch (v.getId()) {            case R.id.one:                line1.setVisibility(View.VISIBLE);                url = SINA;                break;            case R.id.two:                line2.setVisibility(View.VISIBLE);                url = QQ;                break;            case R.id.three:                line3.setVisibility(View.VISIBLE);                url = BAIDU;                break;            default:                break;        }        webView.loadUrl(url);    }    private void reset(){        line1.setVisibility(View.GONE);        line2.setVisibility(View.GONE);        line3.setVisibility(View.GONE);    }}

这里关于底部View内容更新,WebView 通过加载不同URL实现不同视图效果,只是作为Demo测试。

这里对滑动冲突的解决方法,是由内而外的展开,默认使顶层View失去拦截能力,在由底部View的滑动距离,做出不同逻辑判断控制了顶层的拦截与否;这也是比较容易理解和实现的思路。当然,对于此类滑动冲突,有很多不同思路,这里只是列举其一。

实际开发开发中,这种带有同方向滑动特性的控件嵌套时,产生的问题不只是滑动冲突,有时候会有内容显示不全或不显示的情况。

最常见如ScrollView嵌套ListView,这种情况只需自定义ListView使其高度计算为一个很大的值,某种意义上让其失去了滑动的特性,但是这样也让ListView貌似失去了视图回收机制,这种时候如果加载很多很多很多图片,效果就会很不理想。对于这种情况,通过对ListView添加headView及footView也是一种解决的办法,但也得实际UI情况允许。

ScrollView嵌套RecyclerView时稍微麻烦一点,需要自定义ScrollView,还需要自定义实现LinearLayoutManager。

由此可见,在Android中这种带有类似特性的控件嵌套后,问题还是很多的。对此,Google出了一个叫做NestedScrolling的东西,接下来准备研究一下,看看其在解决滑动冲突方面的优势。



  1. 这里说类ScrollView控件,就是说类似于ScrollView特性的控件,即自带滚动特性的控件。 ↩

更多相关文章

  1. android实现屏幕滑动(类似主屏滑动第一屏---->到第2屏)
  2. Android:如何设置底部控件view随着软键盘的弹出而上移——诺诺"涂
  3. Android自定义控件系列案例【二】
  4. android EditText多行文本输入的若干问题
  5. Android(安卓)控件自动“移入、暂停、移出”效果的实现
  6. Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来
  7. 滑轮控件研究二、GestureDetector的深入研究
  8. android SoftKeypad 软键盘的问题
  9. Android(安卓)TextClock的坑(系统12小时强制转换成24小时格式)

随机推荐

  1. Android(安卓)处理 Button 单击事件的三
  2. android批量插入数据效率对比
  3. adnroid(10)(android下的单元测试)
  4. android之壁纸机制
  5. Android使用AsyncTask下载图片并显示进度
  6. Android(安卓)Tools
  7. Android(安卓)显示系统 --- Surface Flin
  8. Android中文API —— VideoView
  9. 下半年我想做的事
  10. Android(安卓)Studio导入Android平台源码