第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > Android 自定义LinearLayout和Behavior实现嵌套滑动

Android 自定义LinearLayout和Behavior实现嵌套滑动

时间:2019-05-30 20:48:29

相关推荐

Android 自定义LinearLayout和Behavior实现嵌套滑动

文章目录

1 实现NestedScrollingParent2接口2 自定义MyBehavior3 新增TextViewBehavior4 添加Behavior限制条件5 最终实现

嵌套滑动事件传递过程如下图所示:

1 实现NestedScrollingParent2接口

自定义LinearLayout,实现NestedScrollingParent2接口,先让onStartNestedScroll方法返回false

public class MyNestedLinearLayout extends LinearLayout implements NestedScrollingParent2 {public MyNestedLinearLayout(Context context) {super(context);}public MyNestedLinearLayout(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public MyNestedLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean onStartNestedScroll(@NonNull View view, @NonNull View view1, int i, int i1) {Log.i("hongx", "11111111111111--->调用了onStartNestedScroll方法");return false;}@Overridepublic void onNestedScrollAccepted(@NonNull View view, @NonNull View view1, int i, int i1) {Log.i("hongx", "22222222222222--->调用了onNestedScrollAccepted方法");}@Overridepublic void onStopNestedScroll(@NonNull View view, int i) {Log.i("hongx", "33333333333333--->调用了onStopNestedScroll方法");}@Overridepublic void onNestedScroll(@NonNull View view, int i, int i1, int i2, int i3, int i4) {Log.i("hongx", "44444444444444--->调用了onNestedScroll方法");}@Overridepublic void onNestedPreScroll(@NonNull View view, int i, int i1, @NonNull int[] ints, int i2) {Log.i("hongx", "555555555555555--->调用了onNestedPreScroll方法");}}

布局文件如下:

<?xml version="1.0" encoding="utf-8"?><com.hongx.behavior.MyNestedLinearLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:my="/apk/res-auto"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><TextViewandroid:layout_width="match_parent"android:layout_height="100dp"android:background="@color/colorAccent"android:gravity="center"android:text="Hello World!"android:textColor="@android:color/white" /><androidx.core.widget.NestedScrollViewandroid:id="@+id/scollView"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/colorPrimary"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:text="111" /><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:text="222" /><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:text="333" /><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:text="444" /><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:text="555" /><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:text="666" /><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:text="777" /></LinearLayout></androidx.core.widget.NestedScrollView></com.hongx.behavior.MyNestedLinearLayout>

滑动NestedScrollView,查看打印结果:

结果只调用了onStartNestedScroll方法,并没有调用其他方法。

将onStartNestedScroll方法返回值设置为true后查看打印结果,如下:

嵌套滑动抽象化:

2 自定义MyBehavior

先设置behavior的自定义属性:

<?xml version="1.0" encoding="utf-8"?><resources><declare-styleable name="MyNestedLinearLayout"><attr name="layout_behavior" format="string"></attr></declare-styleable></resources>

自定义一个MyBehavior,这个MyBehavior的作用是改变TextView的显示。

public class MyBehavior {public MyBehavior(Context context) {}/*** 观察者*/public void setV(View child) {TextView textView = (TextView) child;textView.setText("变化了");}}

MyNestedLinearLayout如下:

public class MyNestedLinearLayout extends LinearLayout implements NestedScrollingParent2 {public MyNestedLinearLayout(Context context) {super(context);}public MyNestedLinearLayout(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public MyNestedLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 这个是嵌套滑动控制事件分发的控制方法,只有返回true才能接收到事件分发** @param view 包含target的ViewParent的直接子View 嵌套滑動的子控件* @param view1 嵌套滑動的子控件* @param i滑动的方向,数值和水平方向 這裡說的不是手勢 而是當前控件的需求 或者環境* @param i1 发起嵌套事件的类型 分为触摸(ViewParent.TYPE_TOUCH)和非触摸(ViewParent.TYPE_NON_TOUCH)* @return*/@Overridepublic boolean onStartNestedScroll(@NonNull View view, @NonNull View view1, int i, int i1) {Log.i("hongx", "11111111111111--->调用了onStartNestedScroll方法");return true;}@Overridepublic void onNestedScrollAccepted(@NonNull View view, @NonNull View view1, int i, int i1) {Log.i("hongx", "22222222222222--->调用了onNestedScrollAccepted方法");}@Overridepublic void onStopNestedScroll(@NonNull View view, int i) {Log.i("hongx", "33333333333333--->调用了onStopNestedScroll方法");}/*** 在子View滑动过程中会通知这个嵌套滑动的方法,要想这里收到嵌套滑动事件必须在onStartNestedScroll返回true** @param view 當前滑動的控件* @param i 滑動的控件在水平方向已经消耗的距离* @param i1 滑動的控件在垂直方法已经消耗的距离* @param i2 滑動的控件在水平方向剩下的未消耗的距离* @param i3 滑動的控件在垂直方法剩下的未消耗的距离* @param i4 发起嵌套事件的类型 分为触摸(ViewParent.TYPE_TOUCH)和非触摸(ViewParent.TYPE_NON_TOUCH)*/@Overridepublic void onNestedScroll(@NonNull View view, int i, int i1, int i2, int i3, int i4) {//遍历它所有的子控件 然后去看子控件 有没有设置Behavior 这个Behavior就是去操作子控件作出动作//得到当前控件中所有的子控件的数量int childCount = this.getChildCount();Log.i("hongx", "childCount = " + childCount);//遍历所有的子控件for (int x = 0; x < childCount; x++) {//得到子控件View childAt = this.getChildAt(x);//获取到子控件的属性对象MyLayoutParams layoutParams = (MyLayoutParams) childAt.getLayoutParams();MyBehavior behavior = layoutParams.behavior;if (behavior != null) {Log.i("hongx", "behavior != null");behavior.setV(childAt);}}}@Overridepublic void onNestedPreScroll(@NonNull View view, int i, int i1, @NonNull int[] ints, int i2) {Log.i("hongx", "555555555555555--->调用了onNestedPreScroll方法");}/*** 这个方法的作用其实就是定义当前你这个控件下所有的子控件使用的LayoutParams类** @param attrs* @return*/@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new MyLayoutParams(getContext(), attrs);}class MyLayoutParams extends LayoutParams {private MyBehavior behavior;public MyLayoutParams(Context c, AttributeSet attrs) {super(c, attrs);//将自定义的属性交给一个TypedArray来管理TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.MyNestedLinearLayout);//通过TypedArray获取到我们定义的属性的值 Behavoir类名String className = a.getString(R.styleable.MyNestedLinearLayout_layout_behavior);//根据类名 将Behavoir实例化behavior = parseBehavior(c, attrs, className);//清空 不清空占内存a.recycle();}/*** 将Behavoir实例化*/private MyBehavior parseBehavior(Context c, AttributeSet attrs, String className) {MyBehavior behavior = null;if (TextUtils.isEmpty(className)) {return null;}try {Class aClass = Class.forName(className);if (!MyBehavior.class.isAssignableFrom(aClass)) {return null;}//去获取到它的构造方法Constructor<? extends MyBehavior> constructor = aClass.getConstructor(Context.class);constructor.setAccessible(true);behavior = constructor.newInstance(c);} catch (Exception e) {e.printStackTrace();}return behavior;}public MyLayoutParams(int width, int height) {super(width, height);}public MyLayoutParams(int width, int height, float weight) {super(width, height, weight);}public MyLayoutParams(ViewGroup.LayoutParams p) {super(p);}public MyLayoutParams(MarginLayoutParams source) {super(source);}@RequiresApi(api = Build.VERSION_CODES.KITKAT)public MyLayoutParams(LayoutParams source) {super(source);}}}

布局文件做如下修改:

<?xml version="1.0" encoding="utf-8"?><com.hongx.behavior.MyNestedLinearLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:my="/apk/res-auto"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><TextViewandroid:layout_width="match_parent"android:layout_height="100dp"android:background="@color/colorAccent"android:gravity="center"android:text="观察者"android:textColor="@android:color/white"my:layout_behavior="com.hongx.behavior.MyBehavior" />...

从效果可以看出,滚动NestedScrollView的时候,TextView的内容改变了。

3 新增TextViewBehavior

再定义个TextViewBehavior继承MyBehavior:

public class TextViewBehavior extends MyBehavior {public TextViewBehavior(Context context) {super(context);}@Overridepublic void setV(View child) {TextView textView = (TextView) child;textView.setVisibility(View.GONE);}}

添加页一个TextView使用TextViewBehavior:

<TextViewandroid:layout_width="match_parent"android:layout_height="100dp"android:background="@color/colorAccent"android:gravity="center"android:text="观察者1"android:textColor="@android:color/white"my:layout_behavior="com.hongx.behavior.TextViewBehavior" />

4 添加Behavior限制条件

public class MyBehavior {public MyBehavior(Context context) {}public boolean layoutDependsOn(@NonNull View parent, @NonNull View child, @NonNull View dependency) {return dependency instanceof NestedScrollView && dependency.getId() == R.id.scollView;}/*** 观察者*/public void setV(View child) {TextView textView = (TextView) child;textView.setText("变化了");}}

在MyNestedLinearLayout中设置这个限制条件,只有当滑动的控件(被观察者)是ScrollView,才去执行Behavior中的方法,让TextView(观察者)改变状态

public class TextViewBehavior extends MyBehavior {public TextViewBehavior(Context context) {super(context);}@Overridepublic boolean layoutDependsOn(@NonNull View parent, @NonNull View child, @NonNull View dependency) {return dependency instanceof RecyclerView;//如果滑动的是RecyclerView才会执行Behavior中的setV方法}@Overridepublic void setV(View child) {TextView textView = (TextView) child;textView.setVisibility(View.GONE);}}

可以看到,第一个TextView变化了,而第二个TextView并没有任何变化。

5 最终实现

MyNestedLinearLayout中的onNestedScroll方法接收到的各个参数值传递给自定义的Behavior。

/*** 在子View滑动过程中会通知这个嵌套滑动的方法,要想这里收到嵌套滑动事件必须在onStartNestedScroll返回true** @param target 當前滑動的控件* @param dxConsumed 滑動的控件在水平方向已经消耗的距离* @param dyConsumed 滑動的控件在垂直方法已经消耗的距离* @param dxUnconsumed 滑動的控件在水平方向剩下的未消耗的距离* @param dyUnconsumed 滑動的控件在垂直方法剩下的未消耗的距离* @param type 发起嵌套事件的类型 分为触摸(ViewParent.TYPE_TOUCH)和非触摸(ViewParent.TYPE_NON_TOUCH)*/@Overridepublic void onNestedScroll(View target, int dxConsumed, int dyConsumed,int dxUnconsumed, int dyUnconsumed, int type) {int childCount = this.getChildCount();//遍历直接子控件for (int x = 0; x < childCount; x++) {View childAt = this.getChildAt(x);//当前属性对象是没有自定义属性的!!!!!MyLayoutParams lp = (MyLayoutParams) childAt.getLayoutParams();//获取到控件的myBehavior对象MyBehavior myBehavior = lp.behavior;//如果子控件设置了myBehaviorif (myBehavior != null) {//判断当前的滑动的控件是不是当前子控件的被观察者if (myBehavior.layoutDependsOn(this, childAt, target)) {myBehavior.onNestedScroll(this, childAt, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);}}}}

在MyBehavior的onNestedScroll方法中实现想要的效果。

/*** 嵌套滑动中的方法** @param parent* @param child* @param target* @param dxConsumed* @param dyConsumed* @param dxUnconsumed* @param dyUnconsumed*/public void onNestedScroll(@NonNull View parent, @NonNull View child, @NonNull View target,int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {//向下滑动了 滑动距离是负数 就是向下if (dyConsumed < 0) {//当前观察者控件的Y坐标小于等于0 并且 被观察者的Y坐标不能超过观察者控件的高度if (child.getY() <= 0 && target.getY() <= child.getHeight()) {child.setTranslationY(-(target.getScrollY() > child.getHeight() ?child.getHeight() : target.getScrollY()));target.setTranslationY(-(target.getScrollY() > child.getHeight() ?child.getHeight() : target.getScrollY()));ViewGroup.LayoutParams layoutParams = target.getLayoutParams();layoutParams.height = (int) (parent.getHeight() - child.getHeight() - child.getTranslationY());target.setLayoutParams(layoutParams);}} else {//向上滑动了 被观察者的Y坐标不能小于或者等于0if (target.getY() > 0) {//设置观察者的Y坐标的偏移 1.不能超过观察者自己的高度child.setTranslationY(-(target.getScrollY() > child.getHeight() ?child.getHeight() : target.getScrollY()));target.setTranslationY(-(target.getScrollY() > child.getHeight() ?child.getHeight() : target.getScrollY()));//获取到被观察者的LayoutParamsViewGroup.LayoutParams layoutParams = target.getLayoutParams();//当我们向上滑动的时候 被观察者的高度 就等于 它父亲的高度 减去观察者的高度 再减去观察者Y轴的偏移值layoutParams.height = (int) (parent.getHeight() - child.getHeight() - child.getTranslationY());target.setLayoutParams(layoutParams);}}}

最后效果如下所示:

Github:/345166018/AndroidUI/tree/master/HxBehavior

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。