自定义 Behavior,伸缩的搜索框
zhouhongid123
8年前
<p>悬浮 + 伸缩 的搜索框</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/089bbf83ce079592f8a70a89a31fb81b.gif"></p> <p style="text-align:center">GIF.gif</p> <p>前几天 用华为商店, 无意中发现了这个效果, 觉得挺赞的,简单来说,就是 搜索框在滑动到某个位置的时候,触发展开和折叠的效果.</p> <p>FloatExpandSearchBehavior</p> <pre> <code class="language-java">package org.alex.behavior.floatexpandsearch; import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.Nullable; import android.support.design.widget.CoordinatorLayout; import android.support.v4.view.ViewCompat; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.AttributeSet; import android.view.View; import android.view.animation.Interpolator; import org.alex.mdlibrary.R; import org.alex.util.LogTrack; import java.lang.ref.WeakReference; /** * 作者:Alex * 时间:2017/2/27 22:30 * 简述: */ public class FloatExpandSearchBehavior extends CoordinatorLayout.Behavior<View> { private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); private int dependentViewId; /** * 依赖 控件 */ private WeakReference<View> dependentViewWeakReference; private WeakReference<View> childViewWeakReference; private int childWidth; private float childY; private int targetY; private int offsetY; private int dependencyHeight; private float lastProgress; private float progress; private float childTopMargin; private IFloatExpandSearch floatExpandSearch; /** * Default constructor for instantiating Behaviors. */ public FloatExpandSearchBehavior() { } /** * Default constructor for inflating Behaviors from layout. The Behavior will have * the opportunity to parse specially defined layout parameters. These parameters will * appear on the child view tag. * * @param context * @param attrs */ public FloatExpandSearchBehavior(Context context, AttributeSet attrs) { super(context, attrs); initBehavior(context, attrs); } private void initBehavior(Context context, AttributeSet attrs) { childTopMargin = -1; dependencyHeight = -1; lastProgress = -1F; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FloatExpandSearchBehavior); dependentViewId = typedArray.getResourceId(R.styleable.FloatExpandSearchBehavior_fesb_dependentViewId, -1); targetY = typedArray.getDimensionPixelOffset(R.styleable.FloatExpandSearchBehavior_fesb_targetY, 0); offsetY = typedArray.getDimensionPixelOffset(R.styleable.FloatExpandSearchBehavior_fesb_offsetY, 0); typedArray.recycle(); } /** * 我们将 该 Behavior 绑定给一个控件, * 在 CoordinateLayout 中,与所绑定的控件 同一 目录级别的 控件中, 确定 所依赖的 * 确定所提供的子视图是否有另一个特定的同级视图作为布局从属,确定你依赖哪些View。 * 返回 true 即可确立依赖关系 */ @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { boolean isHeadView = dependency.getId() == dependentViewId; floatExpandSearch = (IFloatExpandSearch) child; childViewWeakReference = (childViewWeakReference == null) ? new WeakReference<>(dependency) : childViewWeakReference; if (isHeadView) { dependentViewWeakReference = (dependentViewWeakReference == null) ? new WeakReference<>(dependency) : dependentViewWeakReference; } return isHeadView; } @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; } @Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); //offsetChildView(coordinatorLayout, child, dependency); //LogTrack.i("--"); autoShowOrHide(coordinatorLayout, child); } /** * 子控件 需要发生改变 返回 true * * @param parent * @param child * @param dependency * @return */ @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { offsetChildView(parent, child, dependency); autoShowOrHide(parent, child); return true; } private void autoShowOrHide(final CoordinatorLayout parent, final View child) { lastProgress = (lastProgress < 0) ? progress : lastProgress; int floatEnum = floatExpandSearch.getFloatEnum(); LogTrack.i(progress + " " + floatEnum + " " + (progress == 0F)); if (progress == 0F && floatExpandSearch.isUnFolded()) { floatExpandSearch.startFolding(); } else if (progress == 1F) { //LogTrack.i(progress); floatExpandSearch.startUnFolding(-1); } if (lastProgress > progress && progress < 0.5F) { if (IFloatExpandSearch.FloatEnum.FOLDED_2_UNFOLDED == floatEnum) { /*正在由折叠状态 -> 展开状态 需要变成 折叠状态*/ //LogTrack.i("正在由折叠状态 -> 展开状态 需要变成 折叠状态"); floatExpandSearch.cancelUnFolding(); hide(child); } else if (IFloatExpandSearch.FloatEnum.UNFOLDED == floatEnum) { /*正处于展开状态 要变成 折叠状态*/ //LogTrack.i("正处于展开状态 要变成 折叠状态"); hide(child); } else { //LogTrack.i("往上推 隐藏 " + floatEnum); } } else if (lastProgress < progress && progress > 0.5F) { if (IFloatExpandSearch.FloatEnum.UNFOLDED_2_FOLDED == floatEnum) { /*正在由展开状态 -> 折叠状态 需要变成 展开状态*/ //LogTrack.i("正在由展开状态 -> 折叠状态 需要变成 展开状态"); floatExpandSearch.cancelFolding(); show(child); } else if (IFloatExpandSearch.FloatEnum.FOLDED == floatEnum) { /*正处于折叠状态 要变成 展开状态*/ //LogTrack.i("正处于折叠状态 要变成 展开状态"); show(child); } else { //LogTrack.i("往下拉 展示 " + floatEnum); } } lastProgress = progress; } private void offsetChildView(final CoordinatorLayout parent, final View child, final View dependentView) { dependencyHeight = (dependencyHeight < 0) ? dependentView.getMeasuredHeight() : dependencyHeight; childWidth = (childWidth < 0) ? child.getMeasuredWidth() : childWidth; childY = (childY < 0) ? child.getY() : childY; progress = (dependencyHeight - offsetY + dependentView.getY()) * 100 / (dependencyHeight - offsetY); progress *= 0.01F; //LogTrack.i(progress); CoordinatorLayout.LayoutParams childLayoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); childTopMargin = (childTopMargin < 0) ? childLayoutParams.topMargin : childTopMargin; childLayoutParams.topMargin = (int) (childTopMargin * progress); childLayoutParams.topMargin = (childLayoutParams.topMargin >= targetY) ? childLayoutParams.topMargin : targetY; child.setLayoutParams(childLayoutParams); } private View getChildView() { return childViewWeakReference.get(); } /** * 隐藏时的动画 */ private void hide(final View view) { floatExpandSearch.startFolding(); } /** * 显示时的动画 */ private void show(final View view) { floatExpandSearch.startUnFolding(); } @Nullable private View getDependentView() { return (dependentViewWeakReference == null) ? null : dependentViewWeakReference.get(); } private int getDependentHeight() { View dependentView = getDependentView(); return dependentView == null ? 0 : dependentView.getMeasuredHeight(); } }</code></pre> <p>FloatExpandSearchBehavior</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="FloatExpandSearchBehavior"> <attr name="fesb_dependentViewId" format="reference"/> <attr name="fesb_targetY" format="dimension"/> <attr name="fesb_offsetY" format="dimension"/> </declare-styleable> </resources></code></pre> <p>SearchLayout</p> <pre> <code class="language-java">package com.alex.materialdesign.widget; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.GradientDrawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; import com.alex.materialdesign.R; import com.alex.materialdesign.callback.SimpleAnimatorListener; import org.alex.behavior.floatexpandsearch.IFloatExpandSearch; /** * 作者:Alex * 时间:2017/2/27 19:55 * 简述: */ public class SearchLayout extends RelativeLayout implements IFloatExpandSearch { /** * 展开 动画 */ private AnimatorSet unFoldingAnimatorSet; /** * 折叠 动画 */ private AnimatorSet foldingAnimatorSet; private View runnerView; private int currRunnerViewWidth; private int runnerViewWidth; private int runnerViewHeight; /** * 折叠 时间 */ private int foldingDuration; /** * 展开 时间 */ private int unFoldingDuration; private ObjectAnimator foldingXObjectAnimator; private ObjectAnimator unFoldingXObjectAnimator; private int floatEnum; private GradientDrawable runnerDrawable; /** * 折叠 状态的 颜色 */ private int foldingAlpha; /** * 展开 状态的 颜色 */ private int unFoldingAlpha; private ObjectAnimator foldingColorObjectAnimator; private ObjectAnimator unFoldingColorObjectAnimator; /** * 不让 折叠 */ private boolean isOnShowCannotFold; public SearchLayout(Context context) { super(context); initView(context, null); } public SearchLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { foldingAlpha = 255; unFoldingAlpha = 200; foldingDuration = 500; unFoldingDuration = 500; runnerViewWidth = -1; runnerViewHeight = -1; floatEnum = FloatEnum.FOLDED; View childView = LayoutInflater.from(context).inflate(R.layout.layout_search, this, false); runnerView = childView.findViewById(R.id.search_runner); runnerDrawable = (GradientDrawable) runnerView.getBackground(); addView(childView); setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } /** * 开始 展开 * * @param duration */ @Override public void startUnFolding(int duration) { if (duration <= 0) { floatEnum = FloatEnum.UNFOLDED; setRunnerViewWidth(runnerViewWidth); return; } floatEnum = FloatEnum.FOLDED_2_UNFOLDED; unFoldingAnimatorSet = (unFoldingAnimatorSet == null) ? new AnimatorSet() : unFoldingAnimatorSet; unFoldingColorObjectAnimator = ObjectAnimator.ofInt(this, "runnerViewColor", foldingAlpha, unFoldingAlpha); if (unFoldingColorObjectAnimator == null) { } if (unFoldingXObjectAnimator == null) { unFoldingXObjectAnimator = ObjectAnimator.ofInt(this, "runnerViewWidth", runnerViewHeight, runnerViewWidth); } unFoldingAnimatorSet.addListener(new SimpleAnimatorListener() { @Override public void onAnimationEnd(Animator animation) { floatEnum = FloatEnum.UNFOLDED; } }); unFoldingAnimatorSet.play(unFoldingXObjectAnimator).before(unFoldingColorObjectAnimator); unFoldingAnimatorSet.setDuration(duration); if (!unFoldingAnimatorSet.isRunning()) { unFoldingAnimatorSet.start(); } } /** * 开始 展开 */ @Override public void startUnFolding() { startUnFolding(unFoldingDuration); } public boolean isOnShowCannotFold() { return isOnShowCannotFold; } @Override public void setOnShowCannotFold(boolean onShowCannotFold) { isOnShowCannotFold = onShowCannotFold; } /** * 开始 折叠 * * @param duration */ @Override public void startFolding(int duration) { if (duration <= 0) { floatEnum = FloatEnum.FOLDED; setRunnerViewWidth(runnerViewHeight); return; } floatEnum = FloatEnum.UNFOLDED_2_FOLDED; foldingAnimatorSet = (foldingAnimatorSet == null) ? new AnimatorSet() : foldingAnimatorSet; foldingColorObjectAnimator = ObjectAnimator.ofInt(this, "runnerViewColor", unFoldingAlpha, foldingAlpha); if (foldingColorObjectAnimator == null) { } if (foldingXObjectAnimator == null) { //setRunnerViewColor foldingXObjectAnimator = ObjectAnimator.ofInt(this, "runnerViewWidth", runnerViewWidth, runnerViewHeight); } foldingAnimatorSet.addListener(new SimpleAnimatorListener() { @Override public void onAnimationEnd(Animator animation) { floatEnum = FloatEnum.FOLDED; } }); foldingAnimatorSet.play(foldingXObjectAnimator).before(foldingColorObjectAnimator); foldingAnimatorSet.setDuration(duration); if (!foldingAnimatorSet.isRunning()) { foldingAnimatorSet.start(); } } /** * 开始 折叠 */ @Override public void startFolding() { startFolding(foldingDuration); } /** * 取消 折叠 动画 */ @Override public void cancelFolding() { if (foldingAnimatorSet != null && foldingAnimatorSet.isRunning()) { foldingAnimatorSet.cancel(); } } /** * 取消 展开 动画 */ @Override public void cancelUnFolding() { if (unFoldingAnimatorSet != null && unFoldingAnimatorSet.isRunning()) { unFoldingAnimatorSet.cancel(); } } /** * 获取 搜索框的 状态 * * @return */ @Override public int getFloatEnum() { return floatEnum; } @Override public boolean isUnFolded() { return currRunnerViewWidth!=runnerViewHeight; } public void autoFoldedOrUnFolded(){ if(floatEnum == FloatEnum.FOLDED){ startUnFolding(); }else if(floatEnum == FloatEnum.UNFOLDED){ startFolding(); } } private void setRunnerViewWidth(int width) { //LogTrack.e("width = " + width); currRunnerViewWidth = width; LayoutParams params = new LayoutParams(width, runnerViewHeight); runnerView.setLayoutParams(params); } private void setRunnerViewColor(int alpha) { runnerDrawable.setAlpha(alpha); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measureWidth = measureWidth(0, widthMeasureSpec); int measureHeight = measureHeight(0, heightMeasureSpec); if (runnerViewHeight >= 0 || runnerViewWidth >= 0) { return; } for (int i = 0; i < getChildCount() && i < 1; i++) { View childView = getChildAt(i); ViewGroup.LayoutParams childViewLayoutParams = childView.getLayoutParams(); int widthSpec = 0; int heightSpec = 0; if (childViewLayoutParams.width > 0) { widthSpec = MeasureSpec.makeMeasureSpec(childViewLayoutParams.width, MeasureSpec.EXACTLY); } else if (childViewLayoutParams.width == -1) { widthSpec = MeasureSpec.makeMeasureSpec(measureWidth, MeasureSpec.EXACTLY); } else if (childViewLayoutParams.width == -2) { widthSpec = MeasureSpec.makeMeasureSpec(measureWidth, MeasureSpec.AT_MOST); } if (childViewLayoutParams.height > 0) { heightSpec = MeasureSpec.makeMeasureSpec(childViewLayoutParams.height, MeasureSpec.EXACTLY); } else if (childViewLayoutParams.height == -1) { heightSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY); } else if (childViewLayoutParams.height == -2) { heightSpec = MeasureSpec.makeMeasureSpec(measureWidth, MeasureSpec.AT_MOST); } childView.measure(widthSpec, heightSpec); runnerViewWidth = childView.getMeasuredWidth(); runnerViewHeight = childView.getMeasuredHeight(); } //LogTrack.i(runnerViewWidth + " " + runnerViewHeight); //LogTrack.e(measureWidth + " " + measureHeight); } /** * MeasureSpec.EXACTLY是精确尺寸, 50dip或者为 MATCH_PARENT是, * MeasureSpec.AT_MOST是最大尺寸, WRAP_CONTENT时 * MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView, */ private int measureWidth(int size, int widthMeasureSpec) { int result = size; int widthMode = MeasureSpec.getMode(widthMeasureSpec);// 得到模式 int widthSize = MeasureSpec.getSize(widthMeasureSpec);// 得到尺寸 switch (widthMode) { case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = widthSize; break; } return result; } /** * MeasureSpec.EXACTLY是精确尺寸, 50dip或者为 MATCH_PARENT是, * MeasureSpec.AT_MOST是最大尺寸, WRAP_CONTENT时 * MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView, */ private int measureHeight(int size, int heightMeasureSpec) { int result = size; int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); switch (heightMode) { case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = heightSize; break; } return result; } }</code></pre> <p>activity_float_expand_search</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/8aec00e37c0a0a9bb9c367c782be7740.png"></p> <p>Paste_Image.png</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/ab_layout" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" app:contentScrim="@color/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed" app:statusBarScrim="#4a90e2"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="48dp" android:background="@color/colorPrimary" app:layout_collapseMode="pin" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center|top" android:gravity="center" android:orientation="vertical" android:paddingBottom="40dp" android:paddingTop="190dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Alex" android:textColor="@android:color/white" android:textSize="30dp" android:textStyle="bold"/> </LinearLayout> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <ImageView android:id="@+id/iv_portrait" android:layout_width="108dp" android:layout_height="108dp" android:layout_gravity="top" android:layout_marginTop="50dp" android:background="#009688" android:elevation="10dp" android:src="@drawable/logo_alex_cin" app:layout_behavior="org.alex.behavior.scrollportrait.ScrollPortraitBehavior" app:sb_dependentViewId="@+id/ab_layout" app:sb_offsetY="48dp" app:sb_targetHeight="36dp" app:sb_targetWidth="36dp" app:sb_targetX="36dp" app:sb_targetY="6dp" /> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:background="#FFFFFF" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingMultiplier="1.5" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:text="@string/info_1"/> </LinearLayout> </android.support.v4.widget.NestedScrollView> <com.alex.materialdesign.widget.SearchLayout android:id="@+id/sl" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|right" android:layout_marginTop="230dp" android:layout_marginRight="16dp" android:layout_marginLeft="16dp" app:fesb_dependentViewId="@+id/ab_layout" app:fesb_offsetY="48dp" app:fesb_targetY="56dp" app:layout_behavior="org.alex.behavior.floatexpandsearch.FloatExpandSearchBehavior" /> </android.support.design.widget.CoordinatorLayout> </LinearLayout></code></pre> <p> </p> <p>来自:http://www.jianshu.com/p/ec2ad90e9db7</p> <p> </p>