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的简单使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容