1. 程式人生 > >AndroidUI系列--在DecorView層解決RecyclerView和ScrollView的滑動衝突

AndroidUI系列--在DecorView層解決RecyclerView和ScrollView的滑動衝突

滑動衝突,這個是作安卓的必經之坑。最開始的ListView和ScollView衝突,或者ListView巢狀ListView滑動衝突,再或者ListView和ViewPager的滑動衝突,再或者是GraidView等可滑動控制元件互相巢狀的衝突。解決方案呢,有很多。比如在onTouchEvent中攔截事件。又或者自定義ListView,修改onMesure測量,使它在測量時獲得最大的寬高,這樣可以讓它不滑動。全部展示,當然作為在android摸爬滾打了這麼久的程式猿,這些坑都應該踩過了,而且網上一大堆解決方案,不得不說,這就是開源的好處啊,想著谷歌巴巴把kotlin扶上位了,我們這些苦逼的程式猿,那就只有跟著大部隊走了。沒辦法呀~夾縫裡生存。

這裡寫圖片描述

View的繪製流程,Activity–phonewindow–decorview–contentview,如下圖

這裡寫圖片描述

我們平時在Activity的setContentView就是在ContentViews作文章。那麼我們的衝突就是在這裡,在ContentView裡設定了一個activity_main.xml,為什麼會有滑動衝突呢,那是因為recyclerview和scollview都設定在了activity_main.xml。那麼換個角度,如果把recyclerview加在contentviews和activity_main.xml佈局平級。那麼是不是就不存在滑動衝突了呢,想到就來試試。
首先自定義一個view,用來彈窗。

package com.example.administrator.bounceview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by ShuWen on 2017/5/23.
 */
public class BounceView extends View { private int mArcMaxHeight;//彈窗最高距離 private int mArcHeight;//記錄變換過程的距離 private Paint mPaint;//畫筆 private Path mPath = new Path();//繪製動畫弧度 private BounceAnimatorListener animatorListener;//動畫開始的監聽回撥 private Status status = Status.NONE;//記錄動畫的狀態 public enum Status{ //沒動,上升,下降 NONE,STATUS_UP,STATUS_DOWN } public BounceView(Context context) { super(context); init(); } public BounceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public BounceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } //初始化 private void init() { mPaint = new Paint(); mPaint.setColor(Color.WHITE); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mArcMaxHeight = getResources().getDimensionPixelOffset(R.dimen.m_maxarcheight); } //上升的動畫 public void show(){ status = Status.STATUS_UP; if (animatorListener != null){ this.postDelayed(new Runnable() { @Override public void run() { animatorListener.showContent(); } },600); } ValueAnimator animator = ValueAnimator.ofInt(0,mArcMaxHeight); animator.setDuration(700); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mArcHeight = (int) valueAnimator.getAnimatedValue(); if (mArcHeight == mArcMaxHeight){ bounce(); } invalidate(); } }); animator.start(); } //下降的動畫 private void bounce() { status = Status.STATUS_DOWN; ValueAnimator valueAnimator = ValueAnimator.ofInt(mArcMaxHeight,0); valueAnimator.setDuration(600); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mArcHeight = (int) valueAnimator.getAnimatedValue(); invalidate(); } }); valueAnimator.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int currentY = 0; switch (status){ case NONE: currentY = 0; break; case STATUS_UP: currentY = (int) (getHeight()*(1 - (float)(mArcHeight/mArcMaxHeight))+mArcMaxHeight); break; case STATUS_DOWN: currentY = mArcMaxHeight; break; } mPath.reset(); mPath.moveTo(0,currentY); mPath.quadTo(getWidth()/2,currentY - mArcHeight,getWidth(),currentY); mPath.lineTo(getWidth(),getHeight()); mPath.lineTo(0,getHeight()); mPath.close(); canvas.drawPath(mPath,mPaint); } public void setAnimatorListener(BounceAnimatorListener animatorListener){ this.animatorListener = animatorListener; } public interface BounceAnimatorListener{ void showContent(); } }

上升過程中,繪製動畫,使用ValueAnimator在回撥裡進行更新介面,呼叫invalidate()。中間使用到了二階貝塞爾曲線,關於貝塞爾其實很簡單的,在網上一搜,當然就有了。

那麼在建立一個類,用來載入BounceVeiw。

package com.example.administrator.bounceview;

import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;

/**
 * Created by ShuWen on 2017/5/23.
 */

public class BounceMenu {
    private RecyclerView recyclerView;
    private BounceView bounceView;
    private ViewGroup parentVG;
    private View rootView;

    private BounceMenu(View view, int resId, final MyAdapter myAdapter) {
        parentVG = findParentVG(view);

        rootView = LayoutInflater.from(view.getContext()).inflate(resId,null,false);
        recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
        bounceView = (BounceView) rootView.findViewById(R.id.bounceview);

        recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
        bounceView.setAnimatorListener(new BounceView.BounceAnimatorListener() {
            @Override
            public void showContent() {
                recyclerView.setVisibility(View.VISIBLE);
                recyclerView.setAdapter(myAdapter);
                recyclerView.scheduleLayoutAnimation();
            }
        });
    }

    public static BounceMenu makeBounce(View view, int resId, final MyAdapter myAdapter){
        return new BounceMenu(view, resId, myAdapter);
    }

    public void show(){
        if (rootView != null){
            parentVG.removeView(rootView);
        }
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        parentVG.addView(rootView,layoutParams);
        bounceView.show();
    }

    private ViewGroup findParentVG(View view) {
        do {
            if (view instanceof FrameLayout){
                //找到decorview的根佈局
                if (view.getId() == android.R.id.content){
                    return (ViewGroup) view;
                }
            }

            if (view != null){
                ViewParent viewParent = view.getParent();
                view = viewParent instanceof View? (View) viewParent :null;
            }
        }while (view!= null);
        return null;
    }
}

在這裡面,傳入需要新增recylerview的跟佈局,通過這個根佈局獲得decorview的contentviews這個佈局,然後在這個佈局上新增recyclerview。這樣就是與activity_main同級,不會有滑動衝突。在bounceview的監聽裡,新增recyclerview的動畫。
那麼再來看看MianAcicity的程式碼:

package com.example.administrator.bounceview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private MyAdapter myAdapter;
    private List<String> stringList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        stringList = new ArrayList<>();

        for (int i = 0; i < 20; i++) {
            stringList.add("阿西吧"+i);
        }

        myAdapter = new MyAdapter(this,stringList) {
            @Override
            protected int ItemLayoutId() {
                return R.layout.item;
            }

            @Override
            protected void onBindHolder(MyViewHolder myViewHolder, int position) {
                TextView textView = myViewHolder.getTextView(R.id.text);
                textView.setText(stringList.get(position));
            }

        };

    }

    public void click(View view){
        BounceMenu bounceMenu = BounceMenu.makeBounce(findViewById(R.id.activity_main),R.layout.bounce_view_layout,myAdapter);
        bounceMenu.show();
    }
}

呼叫就是相當的簡單了。同時還有炫酷的動畫,何樂而不為呢。接下來所有程式碼都貼出來。
activity_main的xml佈局。使用ScrollView,展示主要資料。在每一項的點選事件,觸發彈窗,recyclerview。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.administrator.bounceview.MainActivity">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#465">
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:gravity="center"
                android:textSize="20sp"
                android:onClick="click"
                android:text="你滑動啊"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:gravity="center"
                android:textSize="20sp"
                android:onClick="click"
                android:text="你滑動啊"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:gravity="center"
                android:onClick="click"
                android:textSize="20sp"
                android:text="你滑動啊"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:gravity="center"
                android:textSize="20sp"
                android:onClick="click"
                android:text="你滑動啊"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:gravity="center"
                android:textSize="20sp"
                android:onClick="click"
                android:text="你滑動啊"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:gravity="center"
                android:textSize="20sp"
                android:onClick="click"
                android:text="你滑動啊"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:gravity="center"
                android:textSize="20sp"
                android:onClick="click"
                android:text="你滑動啊"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View> <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:gravity="center"
            android:textSize="20sp"
            android:onClick="click"
            android:text="你滑動啊"
            />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:gravity="center"
                android:textSize="20sp"
                android:onClick="click"
                android:text="你滑動啊"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#fff">
            </View>

        </LinearLayout>
    </ScrollView>

</RelativeLayout>

那麼再看看彈窗的xml。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="290dp"
        android:layout_gravity="bottom">

        <com.example.administrator.bounceview.BounceView
            android:id="@+id/bounceview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipChildren="false"
            android:overScrollMode="never"
            android:layout_alignParentBottom="true"
            android:layoutAnimation="@anim/bounce_layout"
            android:layout_marginTop="70dp">

        </android.support.v7.widget.RecyclerView>
    </RelativeLayout>

</FrameLayout>

這裡就是彈窗了,recyclerview和scrollview同級,不會產生滑動衝突。
這個是解決滑動衝突的一個可行方案,相當不錯。如果覺得動畫不必要,直接去掉動畫,只需要BounceMenu中的一些邏輯就ok了。我會把程式碼放在git上,有興趣的朋友可以自己研究研究。

效果圖: