CoordinatorLayout的简单使用

CoordinatorLayout的简单实用,其中behavior做了一个简单的自定义,原理不说太多,因为还在摸索中,避免误导别人,有兴趣的可以google,下面是代码,代码中有比较详细的注释:

首先,布局的代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff"
    android:fitsSystemWindows="false"
    tools:context="com.chenh.coordinaforlayout.CoordinatorLayoutActivity">

    <ImageView
        android:id="@+id/scrolling_header"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"
        android:src="@mipmap/test"/>

    <LinearLayout
        android:id="@+id/edit_search"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:orientation="horizontal"
        android:visibility="visible"
        app:layout_behavior="@string/test_search_behavior"
        android:background="#fff">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="20dp"
            android:text="搜索关键字"
            android:textColor="#90000000"/>
    </LinearLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycle_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/test_behavior"
        android:background="#fff"/>

</android.support.design.widget.CoordinatorLayout>

接着是两个自定义的behavior:
1、搜索框的behavior

package com.chenh.coordinaforlayout;

import android.content.Context;
import android.content.res.Resources;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;

import com.example.jay.viewoflist.R;

/**
 * 搜索框的Behavior
 *
 * @author chenh
 */

public class TestSearchBehavior extends CoordinatorLayout.Behavior<LinearLayout> {

    //定义子View所依赖的View
    private View dependencyView;
    //定义上下文对象
    private Context mContext;
    private int default_dp_50;
    private int default_dp_70;
    private int default_dp_12;

    public TestSearchBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        default_dp_50 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, mContext
                .getResources().getDisplayMetrics());
        default_dp_70 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, mContext
                .getResources().getDisplayMetrics());
        default_dp_12 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12, mContext
                .getResources().getDisplayMetrics());
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, LinearLayout child, View dependency) {
        if (dependency != null && dependency.getId() == R.id.scrolling_header) {
            dependencyView = dependency;
            return true;
        }
        return false;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View
            dependency) {
        //计算剩下的滑动距离的比例
        float progress = 1.f - Math.abs(dependencyView.getTranslationY() / (dependencyView
                .getHeight() - default_dp_50));
        //获取需要移动的距离
        float translateY = 20 + (dependencyView.getHeight() - default_dp_70) * progress;
        child.setTranslationY(translateY);
        //获取子View的margin
        int margin = default_dp_12;
        //获取LayoutParam对象
        CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child
                .getLayoutParams();
        //设置margin
        lp.setMargins((int) (margin * progress), 0, (int) (margin * progress), 0);
        child.setLayoutParams(lp);
        return true;
    }
}

2、这个就是RecyclerView的behavior

package com.chenh.coordinaforlayout;

import android.content.Context;
import android.os.Handler;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.Scroller;

import com.example.jay.viewoflist.R;

/**
 * RecyclerView的BeHavior
 *
 * @author chenh
 */

public class TestBehavior extends CoordinatorLayout.Behavior<RecyclerView> {

    //子View所依赖的View
    private View dependencyView;
    //定义上下文对象
    private Context mContext;
    //定义Scroller,用于滑动处理
    private Scroller mScroller;
    //定义标识符,用于判断是否已经进行了滑动处理
    private boolean isScroll = false;
    //Handler,用于更新UI
    private Handler mHandler;
    //默认的偏移量
    private int defaut_dp_50;

    /**
     * 构造器
     *
     * @param context
     * @param attrs
     */
    public TestBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mScroller = new Scroller(mContext);
        mHandler = new Handler();
        defaut_dp_50 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, mContext
                .getResources().getDisplayMetrics());
    }

    /**
     * 主要用于获取子View所依赖的View
     *
     * @param parent
     * @param child
     * @param dependency
     * @return
     */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, RecyclerView child, View dependency) {
        if (dependency != null && dependency.getId() == R.id.scrolling_header) {
            dependencyView = dependency;
            return true;
        }
        return false;
    }

    /**
     * 布局子View,也就是在布局中设置了app:layout_behavior属性的View
     *
     * @param parent
     * @param child
     * @param layoutDirection
     * @return
     */
    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, RecyclerView child, int
            layoutDirection) {
        //获取依赖的View的高度
        int dependencyHeight = dependencyView.getHeight();
        //布局子View在父布局中的位置,在这里的bottom属性中的parent.getHeight() +
        //dependencyHeight - defaut_offset是为了将子View延伸到屏幕外面,防止在向上滑动之后底部留下空白
        child.layout(0, dependencyHeight, parent.getWidth(), (int) (parent.getHeight() +
                dependencyHeight - defaut_dp_50));
        return true;
    }

    /**
     * 询问父布局是否要处理这次滑动事件,true表示处理,反之不处理
     *
     * @param coordinatorLayout
     * @param child
     * @param directTargetChild
     * @param target
     * @param nestedScrollAxes
     * @return
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        //如果是垂直滑动,则返回true
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    /**
     * 当父布局要处理此次滑动事件时回调
     *
     * @param coordinatorLayout
     * @param child
     * @param directTargetChild
     * @param target
     * @param nestedScrollAxes
     */
    @Override
    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, RecyclerView child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        //停止动画
        mScroller.abortAnimation();
        //不在进行滑动处理
        isScroll = false;
        super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target,
                nestedScrollAxes);
    }

    /**
     * 子View即将被滑动时调用
     *
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param dx                x轴方向滑动的滑动距离
     * @param dy                x轴方向滑动的距离距离
     * @param consumed          代表消耗的距离,数组下标0代表x轴,1代表y轴
     */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View
            target, int dx, int dy, int[] consumed) {
        //这个判断的主要作用在于防止子View所依赖的View已经滑动回到原来的位置的时候依然可以滑动
        if ((dependencyView.getTranslationY() - dy) < 0) {
            //在依赖的View的范围内上下滑动,都是允许的
            if (Math.abs(dependencyView.getTranslationY()) < dependencyView.getHeight() -
                    defaut_dp_50) {
                dependencyView.setTranslationY(dependencyView.getTranslationY() - dy);
                consumed[1] = dy;
            }
        }
    }

    /**
     * 依赖的View发生改变时回调
     *
     * @param parent
     * @param child
     * @param dependency
     * @return
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, RecyclerView child, View
            dependency) {
        //子View随着所依赖的View的偏移进行移动
        child.setTranslationY(dependency.getTranslationY());
        //计算剩下的滑动距离的比例
        float progress = 1.f - Math.abs(dependency.getTranslationY() / dependency.getHeight());
        //计算所依赖的View的缩放比例,这个可以根据个人喜好来设置
        float scale = 1.f + (1.f - progress);
        //设置缩放
        dependency.setScaleX(scale);
        dependency.setScaleY(scale);
        //设置透明度
        dependency.setAlpha(progress);
        return true;
    }

    /**
     * NSC处理剩下的距离
     * 比如上面还剩下10px,这里 NSC 滚动 2px 后发现已经到头了,于是 NSC 结束其滚动,调用该方法,
     * 并将 NSC 处理剩下的像素数作为参数(dxUnconsumed、dyUnconsumed)传过来,这里传过来的就是 8px。
     * 参数中还会有 NSC 处理过的像素数(dxConsumed、dyConsumed)。这个方法主要处理一些越界后的滚动。
     *
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param dxConsumed
     * @param dyConsumed
     * @param dxUnconsumed
     * @param dyUnconsumed
     */
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View
            target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        //通常情况是不会发生这个状况
        if (dyUnconsumed > 0) {
            return;
        }
        //判断是否已经到达了底部,因为如果依赖的View已经回到了默认状态,那么这个值就会大于0,因为dyUnconsumed这个值一般为负数
        //当getTranslationY()为0就代表已经回到默认状态,那么这个判断就会为false
        if ((dependencyView.getTranslationY() - dyUnconsumed) < 0) {
            dependencyView.setTranslationY(dependencyView.getTranslationY() - dyUnconsumed);
        }
    }

    /**
     * 这个方法经过测试,回调的时机应该是滑动的时候有一定的速度才会进行回调
     *
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param velocityX
     * @param velocityY
     * @param consumed
     * @return
     */
    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, RecyclerView child, View
            target, float velocityX, float velocityY, boolean consumed) {
        return updateScroll();
    }

    /**
     * 这个方法,在手指抬起了之后就会回调,所以,这里是一个手势结束的随后一道关卡
     *
     * @param coordinatorLayout
     * @param child
     * @param target
     */
    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View
            target) {
        if (!isScroll) {
            updateScroll();
        }
    }

    /**
     * 用于处理滑动
     */
    public boolean updateScroll() {
        //获取依赖的View的高度
        int dependencyHeight = dependencyView.getHeight() - defaut_dp_50;
        //依赖的View的偏移量
        float dependencyScrollY = dependencyView.getTranslationY();
        //滑动的距离大于一半
        if (Math.abs(dependencyScrollY) > dependencyHeight / 2) {
            //大于一般的时候,就让他滑动剩下的距离
            mScroller.startScroll(0, (int) dependencyScrollY, 0, (int) (-dependencyHeight -
                    dependencyScrollY));
            mHandler.post(finalRunnable);
        } else {
            mScroller.startScroll(0, (int) dependencyScrollY, 0, (int) (0 - dependencyScrollY));
            mHandler.post(finalRunnable);
        }
        isScroll = true;
        return true;
    }

    /**
     * 执行滑动处理
     */
    private Runnable finalRunnable = new Runnable() {
        @Override
        public void run() {
            if (mScroller.computeScrollOffset()) {
                dependencyView.setTranslationY(mScroller.getCurrY());
                mHandler.post(this);
            } else {
                isScroll = false;
            }
        }
    };

}

接下来就是主界面了

package com.chenh.coordinaforlayout;

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import android.widget.TextView;

import com.example.jay.viewoflist.R;

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

public class CoordinatorLayoutActivity extends Activity {

    //定义RecycleView组件
    private RecyclerView mRecycleView;
    private List<String> mList = new ArrayList<>(50);

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

    /**
     * 初始化
     */
    private void initView() {
        mRecycleView = (RecyclerView) findViewById(R.id.recycle_view);
        for (int i = 0; i < 49; i++) {
            mList.add("recycle view item : " + i);
        }
        mRecycleView.setLayoutManager(new LinearLayoutManager(this));
        mRecycleView.setAdapter(new RecyClerViewAdapter());

    }

    private class RecyClerViewAdapter extends RecyclerView.Adapter<RecyClerViewAdapter
            .MyViewHolder> {


        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemView = View.inflate(CoordinatorLayoutActivity.this, R.layout.item, null);
            return new MyViewHolder(itemView);
        }

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.mTxtView.setText(mList.get(position));
        }

        @Override
        public int getItemCount() {
            return mList.size();
        }

        class MyViewHolder extends RecyclerView.ViewHolder {

            private TextView mTxtView;

            public MyViewHolder(View itemView) {
                super(itemView);
                mTxtView = (TextView) itemView.findViewById(R.id.txt_item);
            }
        }
    }
}

另外的就是,自定义的behavior在strings.xml中设置好全路径,在布局中引用就好

<string name="test_behavior">com.chenh.coordinaforlayout.TestBehavior</string>
<string name="test_search_behavior">com.chenh.coordinaforlayout.TestSearchBehavior</string>

以上就是CoordinatorLayout的简单使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容