在界面开发过程中,我们经常遇到子控件的移动到某一阶段,引起父控件或者其他控件产生动态效果;
在android 5.0 的Material design设计中,就为我们封装了一套这样的接口,即 NestedScrollingParent 和 NestedScrollingChild;
原理比较简单,当子控件需要滑动的时候,即调用startNestedScroll方法,在滑动之前,可以调用dispatchNestedPreScroll方法,来告诉父view是否需要滑动;
那么子view和父view是如果做到沟通的尼,这就需要第三个类来帮忙了 —- NestedScrollingChildHelper
下面的代码实现了 向左边滑动文字,当文字超过边界的时候, 图片收缩
package com.migu.hwj.component;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.migu.hwj.as.leanring.R;
import com.migu.hwj.util.MyLog;
/*
* 演示Android5.0中Material design设计总新增的一些控件和Api
*
* 同时这个类也演示了在XML中如何使用内部类,即在xml中使用内部类有两个注意的地方
* 1: 内部类是static的,public private没关系
* 2:在xml中控件需要使用view这样的标签,并以class标签描述类的完整路径,以$链接内部类
*
* 参考:http://blog.csdn.net/gorgle/article/details/51428515
*/
/*
* NestedScrolling机制
* 作用: 让父View和子View在滚动式进行配合,实现诸如子view滑动一定距离,父view是如何滑动的效果等等
* 主要接口
* NestedScrollingChild
* NestedScrollingParent
* 帮助类
* NestedScrollingChildHelper
* NestedScrollingParentHelper
*
* 子view一般继承NestedScrollingChild接口作为动作的发起者, 父view一般继承NestedScrollingParent作用动作的响应者
* 正因为这种关系,因此当继承NestedScrollingChild接口的子view一些方法触发的时候,继承NestedScrollingParent的父view的一些方法也会被回调
*
* 例如:
* 子view的startNestedScroll会触发父view的onStartNestedScroll
*
*/
public class KitkatActivity extends Activity {
private KitKatNestedScrollingParent mKitKatNestedScrollingParent;
private LinearLayout.LayoutParams mKitNetedParentParams;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nested_scrolling);
}
/*
* Android5.0中Material design设计中配合子view滑动的父控件
*/
public static class KitKatNestedScrollingParent extends LinearLayout implements NestedScrollingParent {
private NestedScrollingParentHelper mNestedScrollingParentHelper;
public KitKatNestedScrollingParent(Context context) {
super(context);
init();
}
public KitKatNestedScrollingParent(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
}
//因为实现NestedScrollingParent接口,而实现的方法
/**
* 回调开始滑动 该方法决定了当前控件是否能接收到其内部View滑动时的参数
*
* @param child 该父VIew 的子View
* @param target 支持嵌套滑动的 VIew
* @param nestedScrollAxes 滑动方向
* @return 是否支持 嵌套滑动
*/
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
MyLog.logD("KitKatNestedScrollingParent onStartNestedScroll");
if (target instanceof KitKatNestedScrollingChild) {
return true;
}
return false;
}
//因为实现NestedScrollingParent接口,而实现的方法
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
MyLog.logD("KitKatNestedScrollingParent onNestedScrollAccepted");
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
}
//因为实现NestedScrollingParent接口,而实现的方法
@Override
public void onStopNestedScroll(View target) {
MyLog.logD("KitKatNestedScrollingParent onStopNestedScroll");
mNestedScrollingParentHelper.onStopNestedScroll(target);
}
//因为实现NestedScrollingParent接口,而实现的方法
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
MyLog.logD("KitKatNestedScrollingParent onNestedScroll");
}
//因为实现NestedScrollingParent接口,而实现的方法
/*
* 该方法的会传入内部View移动的dx,dy 前3个为输入参数,最后一个是输出参数
* consumed : The horizontal and vertical scroll distance consumed by this parent
* 向上滑动 dy > 0, dx应该等于0(如果X坐标没有发生变化)
* 向下滑动 dy < 0, dx应该等于0(如果X坐标没有发生变化)
* 向左滑动 dx > 0, dy应该等于0(如果y坐标没有发生变化)
* 向右滑动 dx < 0, dy应该等于0(如果y坐标没有发生变化)
*
* @param target View that initiated the nested scroll
* @param dx Horizontal scroll distance in pixels
* @param dy Vertical scroll distance in pixels
* @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
*/
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
MyLog.logD("KitKatNestedScrollingParent onNestedPreScroll dx " + dx);
if (dx > 0) {
scrollBy(dx,0);//滚动
consumed[0] = dx;//告诉child我消费了多少
}
}
//因为实现NestedScrollingParent接口,而实现的方法
/*
* 捕获对内部View的fling事件,如果return true则表示拦截掉内部View的事件
*/
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
MyLog.logD("KitKatNestedScrollingParent onNestedFling");
return false;
}
//因为实现NestedScrollingParent接口,而实现的方法
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
MyLog.logD("KitKatNestedScrollingParent onNestedPreFling");
return false;
}
//因为实现NestedScrollingParent接口,而实现的方法
@Override
public int getNestedScrollAxes() {
MyLog.logD("KitKatNestedScrollingParent getNestedScrollAxes");
return mNestedScrollingParentHelper.getNestedScrollAxes();
}
//scrollBy内部会调用scrollTo
@Override
public void scrollTo(int x, int y) {
super.scrollTo(x, y);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
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);
}
}
/*
* Android5.0中Material design设计中子view控件
*/
public static class KitKatNestedScrollingChild extends LinearLayout implements NestedScrollingChild {
//NestedScrolling帮助类,这个类似ontouch里面使用了手势识别帮助类
private NestedScrollingChildHelper mNestedScrollingChildHelper;
//上一次水平滑动距离
private int lastX;
private final int[] offset = new int[2]; //偏移量
private final int[] consumed = new int[2]; //消费
//子view宽
private int childWidth;
public KitKatNestedScrollingChild(Context context) {
super(context);
}
public KitKatNestedScrollingChild(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 设置嵌套滑动是否可用
*
* @param enabled 参数enabled:true表示view使用嵌套滚动,false表示禁用.
*/
@Override
public void setNestedScrollingEnabled(boolean enabled) {
MyLog.logD("KitKatNestedScrollingChild getNestedScrollAxes");
getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}
/**
* 嵌套滑动是否可用
*
* @return
*/
@Override
public boolean isNestedScrollingEnabled() {
MyLog.logD("KitKatNestedScrollingChild isNestedScrollingEnabled");
return getScrollingChildHelper().isNestedScrollingEnabled();
}
/**
* 开始嵌套滑动,
*
* @param axes 表示方向 有一下两种值
* ViewCompat.SCROLL_AXIS_HORIZONTAL 横向哈东
* ViewCompat.SCROLL_AXIS_VERTICAL 纵向滑动
* <p>
* 返回值:true表示本次滚动支持嵌套滚动,false不支持
*/
@Override
public boolean startNestedScroll(int axes) {
MyLog.logD("KitKatNestedScrollingChild startNestedScroll");
return getScrollingChildHelper().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
MyLog.logD("KitKatNestedScrollingChild stopNestedScroll");
getScrollingChildHelper().stopNestedScroll();
}
/**
* 是否有父View 支持 嵌套滑动, 会一层层的网上寻找父View
*
* @return
*/
@Override
public boolean hasNestedScrollingParent() {
MyLog.logD("KitKatNestedScrollingChild hasNestedScrollingParent");
return getScrollingChildHelper().hasNestedScrollingParent();
}
/**
* 在处理滑动之后 调用
*
* @param dxConsumed 表示view消费了x方向的距离长度
* @param dyConsumed 表示view消费了y方向的距离长度
* @param dxUnconsumed 表示滚动产生的x滚动距离还剩下多少没有消费
* @param dyUnconsumed 表示滚动产生的y滚动距离还剩下多少没有消费
* @param offsetInWindow 表示剩下的距离dxUnconsumed和dyUnconsumed使得view在父布局中的位置偏移了多少
* @return
*/
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
MyLog.logD("KitKatNestedScrollingChild dispatchNestedScroll");
return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
/**
* 一般在滑动之前调用, 在ontouch 中计算出滑动距离, 然后调用该方法, 就给支持的嵌套的父View 处理滑动事件
*
* @param dx x 轴上滑动的距离, 相对于上一次事件, 不是相对于 down事件的 那个距离
* @param dy y 轴上滑动的距离
* @param consumed 表示父布局消费的距离 一个数组, 可以传 一个空的 数组, 表示 x 方向 或 y 方向的事件 是否有被消费 consumed[0]表示x方向,consumed[1]表示y方向
* @param offsetInWindow 支持嵌套滑动到额父View 消费 滑动事件后 导致 本 View 的移动距离
* @return 支持的嵌套的父View 是否处理了 滑动事件
*/
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
MyLog.logD("KitKatNestedScrollingChild dispatchNestedPreScroll");
return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
/**
* @param velocityX x 轴上的滑动速度
* @param velocityY y 轴上的滑动速度
* @param consumed 是否被消费
* @return
*/
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
MyLog.logD("KitKatNestedScrollingChild dispatchNestedFling");
return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}
/**
* @param velocityX x 轴上的滑动速度
* @param velocityY y 轴上的滑动速度
* @return
*/
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
MyLog.logD("KitKatNestedScrollingChild dispatchNestedPreFling");
return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
childWidth = this.getMeasuredWidth();
MyLog.logD("KitKatNestedScrollingChild onMeasure childWidth " + childWidth);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
//按下
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getRawX();
break;
//移动
case MotionEvent.ACTION_MOVE:
int x = (int) (event.getRawX());
int dx = x - lastX;
lastX = x;
scrollBy(-dx, 0);
break;
}
return true;
}
//限制滚动范围
@Override
public void scrollTo(int x, int y) {
super.scrollTo(x, y);
//当超出内容显示的时候,通知父控制收放imageview
MyLog.logD("KitKatNestedScrollingChild scrollTo x " + x + " lastX " + lastX);
//向左滑
if(x >= childWidth && x > 0){
//startNestedScroll开始滑动 dispatchNestedPreScroll 通知父控件滑动
startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL);
dispatchNestedPreScroll(lastX, 0, consumed, offset);
}
}
//初始化helper对象
private NestedScrollingChildHelper getScrollingChildHelper() {
if (mNestedScrollingChildHelper == null) {
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
mNestedScrollingChildHelper.setNestedScrollingEnabled(true);
}
return mNestedScrollingChildHelper;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="match_parent"
>
<view
class="com.migu.hwj.component.KitkatActivity$KitKatNestedScrollingParent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:orientation="horizontal">h
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher"/>
<view
class="com.migu.hwj.component.KitkatActivity$KitKatNestedScrollingChild"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="向左滑动可以收缩左边的图片"
android:gravity="center"
android:textColor="#f0f"
android:textSize="20sp"/>
</view>
-->
</view>
</LinearLayout>
package com.migu.hwj.util;
import android.util.Log;
public class MyLog{
private static boolean mOpenLog = true;
private static String mTag = "hwj";
public static void setTag(String tag){
mTag = tag;
}
public static void logD(String message){
if(mOpenLog){
Log.d(mTag,message);
}
}
}