欢迎来到北京快三

二分快三 这交互炸了系列:仿饿了么商家细目页,嵌套起伏机制实战篇

正文:

原标题:这交互炸了系列:仿饿了么商家细目页,嵌套起伏机制实战篇

本文作者

作者:彭冠铭

链接:

https://juejin.im/user/58218f26a22b9d0067e1593f

1

概述

之前的《浅析NestedScrolling嵌套滑动机制之基础篇》 https://www.wanandroid.com/blog/show/2771带行家晓畅NestedScrolling的原理和行使还有它的改进等等,这篇文章手把手基于NestedScrolling嵌套滑动机制现实饿了么v8.27.6商家细目页。

github地址:

https://github.com/pengguanming/ElemeNestedScrolling

终局预览

必要仿写的有两片面,第一个是商家页的嵌套滑动,第二个是商家点餐页的嵌套滑动。

apk下载地址:

https://raw.githubusercontent.com/pengguanming/ElemeNestedScrolling/master/app/release/ElemeNestedScrolling.apk

2

商家页的嵌套滑动实现

终局分析

商家滑动页组织主要有上图五片面构成,逻辑上的层级如图所示有3层。

滑动Content片面时行使View的TransitionY属性转折位置来消耗滑动值,按照Content片面的TransitionY设定各栽周围从而计算百分最近实走颜色渐变、位移、Scale、Alpha终局。

下面来表明上图中变量的意义:

topBarHeight; //topBar高度

contentTransY; //滑动内容初首化TransY

upAlphaScaleY; //上滑时logo,珍藏icon缩放、搜索icon、分享icon透明度临界值

upAlphaGradientY; //上滑时搜索框、topBar背景,返回icon、拼团icon颜色渐变临界值

downFlingCutOffY; //从折叠状态下滑产生fling时回弹到初首状态的最大值

downCollapsedAlphaY; //下滑时折叠内容透明度临界值

downShopBarTransY; //下滑时购物内容位移临界值

downContentAlphaY; //下滑时收首按钮和滑动内容透明度临界值

ivCloseHeight; //收首按钮的高度

content片面的上滑周围=[topBarHeight,contentTransY]

content片面的下滑周围=[contentTransY,满屏高度-ivCloseHeight]

举个按照Content片面的TransitionY计算百分比例子(请仔细:按照Android的坐标系contentTransY>upAlphaScaleY):

/**

*计算上滑时logo,珍藏icon缩放、搜索icon、分享icon透明度的百分比其周围限制在[0.0f,1.0f],

而Content片面的TranslationY值限制在[upAlphaScaleY,contentTransY],v4包的MathUtils类的clamp手段能够协助限制值的周围,超过周围时百分下限为0,上限为1。

*/

upAlphaScalePro= (contentTransY - MathUtils.clamp(Content片面的TranslationY, upAlphaScaleY,

contentTransY)) / (contentTransY-upAlphaScaleY);

//按照upAlphaScalePro竖立icon的透明度

floatalpha = 1- upAlphaScalePro;

setAlpha(mIvShare, alpha);

3

ElemeNestedScrollLayout

商家页的嵌套滑的自定义View继承FrameLayout,命名为ElemeNestedScrollLayout,在Content片面能够滑动的View只有ViewPager里Fragment的RecyclerView和NestedScrollView,它们都实现了NestedScrollingChild2接口,故自定义View要实现NestedScrollingParent2接口按照仔细逻辑来消耗NestedScrollingChild2的滑动值。

组织

下面是组织要点,偏重于控件的尺寸和位置,完善组织请参考:ElemeNestedScrollLayout组织

https://github.com/pengguanming/ElemeNestedScrolling/blob/master/app/src/main/res/layout/activity_my_eleme_layout.xml

< ElemeNestedScrollLayout

android:layout_width= "match_parent"

android:layout_height= "match_parent"

>

< LinearLayout

android:layout_width= "match_parent"

android:layout_height= "match_parent"

android:orientation= "vertical">

<!--Header片面-->

< android.support.constraint.ConstraintLayout

android:layout_width= "match_parent"

android:layout_height= "@dimen/header_height"

...>

<!--Header片面-->

<!--Collasp Content片面-->

<< android.support.constraint.ConstraintLayout

android:layout_width= "match_parent"

android:layout_height= "0dp"

android:layout_weight= "1"

...>

<!--Collasp Content片面-->

</ LinearLayout>

<!--Top Bar片面-->//自定义View二分快三,其作用是在折叠状态时适配沉浸式状态栏终局。

< TopBarLayout

android:layout_width= "match_parent"

android:layout_height= "@dimen/top_bar_height"

...>

<!--Top Bar片面-->

<!--Content片面-->

< android.support.constraint.ConstraintLayout

android:layout_width= "match_parent"

android:layout_height= "match_parent"

android:translationY= "@dimen/content_trans_y">

< com.flyco.tablayout.SlidingTabLayout

android:layout_width= "match_parent"

android:layout_height= "@dimen/stl_height"/>

< android.support.v4.view.ViewPager

android:layout_width= "match_parent"

android:layout_height= "0dp"/>

< ImageView

android:layout_width= "match_parent"

android:layout_height= "@dimen/iv_close_height"/>

</ android.support.constraint.ConstraintLayout>

<!--Content片面-->

<!--Shop Bar片面-->

< android.support.constraint.ConstraintLayout

android:layout_gravity= "bottom"

android:layout_width= "match_parent"

android:layout_height= "@dimen/shop_bar_height"

...>

<!--Shop Bar片面-->

</ ElemeNestedScrollLayout>

绑定必要做终局的View、引入Dimens、测量Content片面的高度

从上面图片能够分析出:

折叠状态时二分快三,Content片面高度=满屏高度-TopBar片面的高度

publicclassElemeNestedScrollLayoutextendsFrameLayoutimplementsNestedScrollingParent2{

//Header片面

privateView mIvLogo;

privateView mVCollect;

//Collaps Content片面

privateView mClCollapsedHeader;

privateView mCollapsedContent;

privateView mRvCollapsed;

//TopBar片面

privateView mIvSearch;

privateView mIvShare;

privateView mTvSearch;

privateView mTopBar;

privateImageView mIvBack;

privateImageView mIvAssemble;

//Content片面

privateView mLlContent;

privateView mIvClose;

privateView mViewPager;

privateView mStl;

//ShopBar片面

privateView mShopBar;

privatefloattopBarHeight; //topBar高度

privatefloatshopBarHeight; //shopBar高度

privatefloatcontentTransY; //滑动内容初首化TransY

privatefloatupAlphaScaleY; //上滑时logo二分快三,珍藏icon缩放、搜索icon、分享icon透明度临界值

privatefloatupAlphaGradientY; //上滑时搜索框、topBar背景,返回icon、拼团icon颜色渐变临界值

privatefloatdownFlingCutOffY; //从折叠状态下滑产生fling时回弹到初首状态的临界值

privatefloatdownCollapsedAlphaY; //下滑时折叠内容透明度临界值

privatefloatdownShopBarTransY; //下滑时购物内容位移临界值

privatefloatdownContentAlphaY; //下滑时收首按钮和滑动内容透明度临界值

privatefloatdownEndY; //下滑时尽头值

@Override

protectedvoidonFinishInflate{

super.onFinishInflate;

mLlContent = findViewById(R.id.cl_content);

mCollapsedContent = findViewById(R.id.cl_collapsed_content);

mIvSearch = findViewById(R.id.iv_search);

mIvShare = findViewById(R.id.iv_share);

mIvBack = findViewById(R.id.iv_back);

mIvAssemble = findViewById(R.id.iv_assemble);

mIvLogo = findViewById(R.id.iv_logo);

mVCollect = findViewById(R.id.iv_collect);

mTvSearch = findViewById(R.id.tv_search);

mTopBar = findViewById(R.id.cl_top_bar);

mClCollapsedHeader = findViewById(R.id.cl_collapsed_header);

mRvCollapsed = findViewById(R.id.rv_collasped);

mIvClose = findViewById(R.id.iv_close);

mViewPager = findViewById(R.id.vp);

mStl = findViewById(R.id.stl);

mShopBar = findViewById(R.id.cl_shop_bar);

}

@Override

protectedvoidonMeasure( intwidthMeasureSpec, intheightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

//竖立Content片面高度

topBarHeight= mTopBar.getMeasuredHeight;

ViewGroup.LayoutParams params = mLlContent.getLayoutParams;

params.height = ( int) (getMeasuredHeight - topBarHeight);

//重新测量

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

@Override

protectedvoidonSizeChanged( intw, inth, intoldw, intoldh) {

super.onSizeChanged(w, h, oldw, oldh);

shopBarHeight = getResources.getDimension(R.dimen.shop_bar_height);

contentTransY = getResources.getDimension(R.dimen.content_trans_y);

downShopBarTransY = contentTransY shopBarHeight;

upAlphaScaleY = contentTransY - dp2px( 32);

upAlphaGradientY = contentTransY - dp2px( 64);

downFlingCutOffY = contentTransY dp2px( 28);

downCollapsedAlphaY = contentTransY dp2px( 32);

downContentAlphaY = getResources.getDimension(R.dimen.donw_content_alpha_y);

downEndY = getHeight - getResources.getDimension(R.dimen.iv_close_height);

}

}

实现NestedScrollingParent2接口

onStartNestedScroll

ElemeNestedScrollLayout只处理Content片面里可滑动View的垂直倾向的滑动。

@Override

publicbooleanonStartNestedScroll(@NonNull View child, @NonNull View target, intaxes, inttype) {

returnchild.getId == mLlContent.getId&&

axes==ViewCompat.SCROLL_AXIS_VERTICAL;

}

onNestedPreScroll

接下来就是处理滑动,上面终局分析挑过:

Content片面的:

上滑周围=[topBarHeight,contentTransY]、

下滑周围=[contentTransY,downEndY(即满屏高度-ivCloseHeight)]

即滑动周围为[topBarHeight,downEndY];

ElemeNestedScrollLayout要限制Content片面的TransitionY值要在周围内,仔细处理如下。

Content片面里可滑动View去上滑动时:

倘若Content片面现在TransitionY View滑动的dy > topBarHegiht,ElemeNestedScrollLayout竖立Content片面的TransitionY为Content片面现在TransitionY View滑动的dy达到移动的终局来消耗View的dy。 倘若Content片面现在TransitionY View滑动的dy = topBarHegiht,ElemeNestedScrollLayout同上操作。 倘若Content片面现在TransitionY View滑动的dy < topBarHegiht,ElemeNestedScrollLayout只消耗片面dy(即Content片面现在TransitionY到topBarHeight差值),盈余的dy让View滑动消耗。

Content片面里可滑动View去下滑动并且View已经不克去下滑动

(比如RecyclerView已经到顶部还去下滑)时:

倘若Content片面现在TransitionY View滑动的dy >= topBarHeight 并且 Content片面现在TransitionY View滑动的dy <= downEndY,ElemeNestedScrollLayout竖立Content片面的TransitionY为Content片面现在TransitionY View滑动的dy达到移动的终局来消耗View的dy。 Content片面现在TransitionY View滑动的dy > downEndY,ElemeNestedScrollLayout只消耗片面dy(即Content片面现在TransitionY到downEndY差值)。

下面是代码实现:

@Override

publicvoidonNestedPreScroll(@NonNull View target, intdx, intdy, @NonNull int[] consumed, inttype) {

floatcontentTransY = mLlContent.getTranslationY - dy;

//处理上滑

if(dy > 0) {

if(contentTransY >= topBarHeight) {

translationByConsume(mLlContent, contentTransY, consumed, dy);

} else{

translationByConsume(mLlContent, topBarHeight, consumed, (mLlContent.getTranslationY - topBarHeight));

}

}

if(dy < 0&& !target.canScrollVertically(- 1)) {

//处理下滑

if(contentTransY >= topBarHeight && contentTransY <= downEndY) {

translationByConsume(mLlContent, contentTransY, consumed, dy);

} else{

translationByConsume(mLlContent, downEndY, consumed, downEndY - mLlContent.getTranslationY);

}

}

}

privatevoidtranslationByConsume(View view, floattranslationY,

int[] consumed, floatconsumedDy) {

consumed[ 1] = ( int) consumedDy;

view.setTranslationY(translationY);

}

就如许处理益了Content片面的滑动,接下来就处理Content片面的滑动过程中各栽View的终局并对外袒露百分比监听接口,比如上滑折叠过程转折状态栏字体就必要用到。

下面的周围值是针对Content片面的TransitionY,按照之前终局分析仔细如下:

竖立logo、珍藏icon缩放,搜索icon、分享icon透明度周围[upAlphaScaleY,contentTransY] 竖立搜索框、topBar背景,返回icon、拼团icon颜色渐周围[upAlphaGradientY,contentTransY] 竖立Collapse Content片面透明度周围[contentTransY,downCollapsedAlphaY] 竖立Shop Bar位移周围[contentTransY,downShopBarTransY] 竖立收首按钮和滑动内容透明度[downContentAlphaY,downEndY]

下面是代码实现:

...

privateArgbEvaluator iconArgbEvaluator; //返回icon、拼团icon颜色渐变的Evaluator

privateArgbEvaluator topBarArgbEvaluator; //topbar颜色渐变的Evaluator

publicElemeNestedScrollLayout( @NonNull Context context) {

this(context, null);

}

publicElemeNestedScrollLayout( @NonNull Context context, @Nullable AttributeSet attrs) {

this(context, attrs, -1);

}

publicElemeNestedScrollLayout( @NonNull Context context, @Nullable AttributeSet attrs, intdefStyleAttr ) {

super(context, attrs, defStyleAttr);

mParentHelper = newNestedScrollingParentHelper( this);

iconArgbEvaluator = newArgbEvaluator;

topBarArgbEvaluator = newArgbEvaluator;

...

}

@ Override

publicvoidonNestedPreScroll( @NonNull View target, intdx, intdy, @NonNull int[] consumed, inttype ) {

...

//按照upAlphaScalePro,竖立logo、珍藏icon缩放,搜索icon、分享icon透明度

floatupAlphaScalePro = getUpAlphaScalePro;

alphaScaleByPro(upAlphaScalePro);

//按照upAlphaGradientPro,竖立topBar背景、返回icon、拼团icon颜色渐变值,搜索框透明度

floatupAlphaGradientPro = getUpAlphaGradientPro;

alphaGradientByPro(upAlphaGradientPro);

//按照downCollapsedAlphaPro,竖立折叠内容透明度

floatdownCollapsedAlphaPro = getDownCollapsedAlphaPro;

alphaCollapsedContentByPro(downCollapsedAlphaPro);

//按照downContentAlphaPro,竖立滑动内容、收首按钮的透明度

floatdownContentAlphaPro = getDownContentAlphaPro;

alphaContentByPro(downContentAlphaPro);

//按照downShopBarTransPro,竖立购物内容内容位移

floatdownShopBarTransPro = getDownShopBarTransPro;

transShopBarByPro(downShopBarTransPro);

//按照upCollapsedContentTransPro,竖立折叠内容位移

floatupCollapsedContentTransPro = getUpCollapsedContentTransPro;

transCollapsedContentByPro(upCollapsedContentTransPro);

if(mProgressUpdateListener!= null){

mProgressUpdateListener.onUpAlphaScaleProUpdate(upAlphaScalePro);

mProgressUpdateListener.onUpAlphaGradientProUpdate(upAlphaGradientPro);

mProgressUpdateListener.onDownCollapsedAlphaProUpdate(downCollapsedAlphaPro);

mProgressUpdateListener.onDownContentAlphaProUpdate(downContentAlphaPro);

mProgressUpdateListener.onDownShopBarTransProUpdate(downShopBarTransPro);

mProgressUpdateListener.onUpCollapsedContentTransProUpdate(upCollapsedContentTransPro);

}

}

/**

* 按照upCollapsedContentTransPro,竖立折叠内容位移

*/

privatevoidtransCollapsedContentByPro( floatupCollapsedContentTransPro ) {

floatcollapsedContentTranY = - (upCollapsedContentTransPro * (contentTransY - topBarHeight));

translation(mCollapsedContent,collapsedContentTranY);

}

/**

* 按照downShopBarTransPro,竖立购物内容内容位移

*/

privatevoidtransShopBarByPro( floatdownShopBarTransPro ) {

floatshopBarTransY = ( 1-downShopBarTransPro) * shopBarHeight;

translation(mShopBar,shopBarTransY);

}

/**

* 按照downContentAlphaPro,竖立滑动内容、收首按钮的透明度

*/

privatevoidalphaContentByPro( floatdownContentAlphaPro ) {

setAlpha(mViewPager,downContentAlphaPro);

setAlpha(mStl,downContentAlphaPro);

setAlpha(mIvClose, 1-downContentAlphaPro);

if(mIvClose.getAlpha== 0){

mIvClose.setVisibility(GONE);

} else{

mIvClose.setVisibility(VISIBLE);

}

}

/**

* 按照downCollapsedAlphaPro,竖立折叠内容透明度

*/

privatevoidalphaCollapsedContentByPro( floatdownCollapsedAlphaPro ) {

setAlpha(mClCollapsedHeader,downCollapsedAlphaPro);

setAlpha(mRvCollapsed, 1- downCollapsedAlphaPro);

}

/**

* 按照upAlphaGradientPro,竖立topBar背景、返回icon、拼团icon颜色渐变值,搜索框透明度

*/

privatevoidalphaGradientByPro( floatupAlphaGradientPro ) {

setAlpha(mTvSearch, upAlphaGradientPro);

inticonColor = ( int) iconArgbEvaluator.evaluate(

upAlphaGradientPro,

getContext.getResources.getColor(R.color.white),

getContext.getResources.getColor(R.color.black)

);

inttopBarColor = ( int) topBarArgbEvaluator.evaluate(

upAlphaGradientPro,

getContext.getResources.getColor(R.color.trans_white),

getContext.getResources.getColor(R.color.white)

);

mTopBar.setBackgroundColor(topBarColor);

mIvBack.getDrawable.mutate.setTint(iconColor);

mIvAssemble.getDrawable.mutate.setTint(iconColor);

}

/**

* 按照upAlphaScalePro,竖立logo、珍藏icon缩放,搜索icon、分享icon透明度

*/

privatevoidalphaScaleByPro( floatupAlphaScalePro ) {

floatalpha = 1- upAlphaScalePro;

floatscale = 1- upAlphaScalePro;

setAlpha(mIvSearch, alpha);

setAlpha(mIvShare, alpha);

setScaleAlpha(mIvLogo, scale, scale, alpha);

setScaleAlpha(mVCollect, scale, scale, alpha);

}

privatefloatgetDownContentAlphaPro{

return(downEndY - MathUtils.clamp(mLlContent.getTranslationY, downContentAlphaY, downEndY)) / (downEndY - downContentAlphaY);

}

privatefloatgetDownCollapsedAlphaPro{

return(downCollapsedAlphaY - MathUtils.clamp(mLlContent.getTranslationY, contentTransY, downCollapsedAlphaY)) / (downCollapsedAlphaY -contentTransY);

}

privatefloatgetDownShopBarTransPro{

return(downShopBarTransY - MathUtils.clamp(mLlContent.getTranslationY, contentTransY, downShopBarTransY)) / (downShopBarTransY -contentTransY);

}

privatefloatgetUpAlphaGradientPro{

return(upAlphaScaleY - MathUtils.clamp(mLlContent.getTranslationY, upAlphaGradientY, upAlphaScaleY)) / (upAlphaScaleY-upAlphaGradientY);

}

privatefloatgetUpAlphaScalePro{

return(contentTransY - MathUtils.clamp(mLlContent.getTranslationY, upAlphaScaleY, contentTransY)) / (contentTransY-upAlphaScaleY);

}

privatefloatgetUpCollapsedContentTransPro{

return(contentTransY - MathUtils.clamp(mLlContent.getTranslationY, topBarHeight, contentTransY)) / (contentTransY-topBarHeight);

}

privatevoidsetAlpha( View view, floatalpha ) {

view.setAlpha(alpha);

}

privatevoidsetScale( View view , floatscaleY, floatscaleX ) {

view.setScaleY(scaleY);

view.setScaleX(scaleX);

}

privatevoidsetScaleAlpha( View view , floatscaleY, floatscaleX, floatalpha ) {

setAlpha(view,alpha);

setScale(view,scaleY,scaleX);

}

privatevoidtranslation( View view, floattranslationY ) {

view.setTranslationY(translationY);

}

publicinterfaceProgressUpdateListener{

voidonUpCollapsedContentTransProUpdate( floatpro ) ;

voidonUpAlphaScaleProUpdate( floatpro ) ;

voidonUpAlphaGradientProUpdate( floatpro ) ;

voidonDownCollapsedAlphaProUpdate( floatpro ) ;

voidonDownContentAlphaProUpdate( floatpro ) ;

voidonDownShopBarTransProUpdate( floatpro ) ;

}

onStopNestedScroll

在下滑Content片面从初首状态转换到打开状态的过程中松手就会实走收首或打开的动画,这逻辑在onStopNestedScroll实现,但仔细倘若动画未实走完毕手指再落下滑动时,答该在onNestedScrollAccepted作废现在实走中的动画。

倘若Content片面的TransitionY异国超过downCollapsedAlphaY,实走收首Content片面动画终局,恢复到初首转态。

倘若Content片面的TransitionY超过了downCollapsedAlphaY,实走打开Content片面动画终局,转换到打开转态。

代码实现如下:

...

publicstaticfinallongANIM_DURATION_FRACTION = 200L;

privateValueAnimator restoreOrExpandAnimator; //收首或打开折叠内容时实走的动画

publicElemeNestedScrollLayout(@NonNull Context context, @Nullable AttributeSet attrs, intdefStyleAttr) {

...

restoreOrExpandAnimator = newValueAnimator;

restoreOrExpandAnimator.setInterpolator( newAccelerateInterpolator);

restoreOrExpandAnimator.addUpdateListener( newValueAnimator.AnimatorUpdateListener {

@Override

publicvoidonAnimationUpdate(ValueAnimator animation){

translation(mLlContent, ( float) animation.getAnimatedValue);

//按照downShopBarTransPro,竖立购物内容内容位移

floatdownShopBarTransPro = getDownShopBarTransPro;

transShopBarByPro(downShopBarTransPro);

//按照downCollapsedAlphaPro,竖立折叠内容透明度

floatdownCollapsedAlphaPro = getDownCollapsedAlphaPro;

alphaCollapsedContentByPro(downCollapsedAlphaPro);

//按照downContentAlphaPro,竖立滑动内容、收首按钮的透明度

floatdownContentAlphaPro = getDownContentAlphaPro;

alphaContentByPro(downContentAlphaPro);

if(mProgressUpdateListener!= null){

mProgressUpdateListener.onDownCollapsedAlphaProUpdate(downCollapsedAlphaPro);

mProgressUpdateListener.onDownContentAlphaProUpdate(downContentAlphaPro);

mProgressUpdateListener.onDownShopBarTransProUpdate(downShopBarTransPro);

}

}

});

}

@Override

publicvoidonNestedScrollAccepted(@NonNull View child, @NonNull View target, intaxes, inttype) {

mParentHelper.onNestedScrollAccepted(child, target, axes, type);

if(restoreOrExpandAnimator.isStarted) {

restoreOrExpandAnimator.cancel;

}

}

@Override

publicvoidonStopNestedScroll(@NonNull View target, inttype) {

mParentHelper.onStopNestedScroll(target, type);

//倘若不是从初首状态转换到打开状态过程触发返回

if(mLlContent.getTranslationY <= contentTransY) {

return;

}

//按照百分比计算动画实走的时长

floatdownCollapsedAlphaPro = getDownCollapsedAlphaPro;

floatdownContentAlphaYPro = getDownContentAlphaPro;

if(downCollapsedAlphaPro == 0) {

expand(( long) (downContentAlphaYPro * ANIM_DURATION_FRACTION));

} else{

restore(( long) (downCollapsedAlphaPro * ANIM_DURATION_FRACTION));

}

}

publicvoidrestore( longdur) {

if(restoreOrExpandAnimator.isStarted) {

restoreOrExpandAnimator.cancel;

}

restoreOrExpandAnimator.setFloatValues(mLlContent.getTranslationY, contentTransY);

restoreOrExpandAnimator.setDuration(dur);

restoreOrExpandAnimator.start;

}

publicvoidexpand( longdur) {

if(restoreOrExpandAnimator.isStarted) {

restoreOrExpandAnimator.cancel;

}

restoreOrExpandAnimator.setFloatValues(mLlContent.getTranslationY, downEndY);

restoreOrExpandAnimator.setDuration(dur);

restoreOrExpandAnimator.start;

}

处理惯性滑动

NestedScrollingParent2处理惯性滑动的手段主要有两栽:

在onNestedPreFling或者onNestedFling返回值为true消耗失踪。 在onNestedPreFling和onNestedFling返回值都为false的前挑下,在onNestedPreScroll或者onNestedScroll消耗失踪,这栽手段能够和清淡的滑动共用逻辑代码。

场景1:迅速去上滑动Content片面的可滑动View产生惯性滑动,这和前线onNestedPreScroll处理上滑的终局一模相通,因此能够复用逻辑,行使第二栽手段处理。

场景2:在折叠状态并Content片面的可滑动View异国滑动到顶部尽头时,迅速去下滑动Content片面的可滑动View产生惯性滑动滑到顶部尽头就停留了,这和前线onNestedPreScroll处理下滑的终局相通,但众了个惯性滑动滑到顶部尽头就停留的条件判定,行使第二栽手段处理。

场景3:迅速去下滑动Content片面的可滑动View转化打开状态产生惯性滑动,这和前线onNestedPreScroll处理下滑的终局相通,行使第二栽手段处理,但仔细在惯性滑动异国十足消耗失踪的时候,会赓续触发onNestedPreScroll来消耗直到惯性滑动十足消耗失踪。

因而当滑动到打开状态的时候要停留Content片面的View滑动由于这时已经是打开状态了,不需View赓续滑动触发onNestedPreScroll,仔细NestedScrollView并异国袒露对外停留滑动的手段,只能逆射获取它的OverScroller停留滑动。

下面是代码实现:

@Override

publicvoidonNestedPreScroll(@NonNull View target, intdx, intdy, @NonNull int[] consumed, inttype) {

floatcontentTransY = mLlContent.getTranslationY - dy;

//处理上滑和场景1

if(dy > 0) {

if(contentTransY >= topBarHeight) {

translationByConsume(mLlContent, contentTransY, consumed, dy);

} else{

translationByConsume(mLlContent, topBarHeight, consumed, (mLlContent.getTranslationY - topBarHeight));

}

}

if(dy < 0&& !target.canScrollVertically(- 1)) {

//下滑时处理Fling,十足折叠时,下滑Recycler(或NestedScrollView) Fling起伏到列外顶部(或视图顶部)停留Fling,对答场景2

if(type == ViewCompat.TYPE_NON_TOUCH&&mLlContent.getTranslationY == topBarHeight) {

stopViewScroll(target);

return;

}

//处理下滑

if(contentTransY >= topBarHeight && contentTransY <= downEndY) {

translationByConsume(mLlContent, contentTransY, consumed, dy);

} else{

translationByConsume(mLlContent, downEndY, consumed, downEndY - mLlContent.getTranslationY);

//对答场景3

if(target instanceofNestedScrollView) {

stopViewScroll(target);

}

}

}

...

}

/**

* 停留View的滑动

*/

privatevoidstopViewScroll(View target){

if(target instanceofRecyclerView) {

((RecyclerView) target).stopScroll;

}

if(target instanceofNestedScrollView) {

try{

Class<? extends NestedScrollView> clazz = ((NestedScrollView) target).getClass;

Field mScroller = clazz.getDeclaredField( "mScroller");

mScroller.setAccessible( true);

OverScroller overScroller = (OverScroller) mScroller.get(target);

overScroller.abortAnimation;

} catch(NoSuchFieldException | IllegalAccessException e) {

e.printStackTrace;

}

}

}

场景4:迅速去下滑动Content片面的可滑动View,从非折叠状态转化打开状态产生惯性滑动,由于有回弹终局,因而逻辑处理和onNestedPreScroll纷歧样,行使第一栽手段处理。

...

privateValueAnimator reboundedAnim; //回弹动画

publicElemeNestedScrollLayout(@NonNull Context context, @Nullable AttributeSet attrs, intdefStyleAttr) {

...

reboundedAnim = newValueAnimator;

reboundedAnim.setInterpolator( newDecelerateInterpolator);

reboundedAnim.addUpdateListener( newValueAnimator.AnimatorUpdateListener {

@Override

publicvoidonAnimationUpdate(ValueAnimator animation){

translation(mLlContent, ( float) animation.getAnimatedValue);

//按照upAlphaScalePro,竖立logo、珍藏icon缩放,搜索icon、分享icon透明度

floatupAlphaScalePro = getUpAlphaScalePro;

alphaScaleByPro(upAlphaScalePro);

//按照upAlphaGradientPro,竖立topBar背景、返回icon、拼团icon颜色渐变值,搜索框透明度

floatupAlphaGradientPro = getUpAlphaGradientPro;

alphaGradientByPro(upAlphaGradientPro);

//按照downCollapsedAlphaPro,竖立折叠内容透明度

floatdownCollapsedAlphaPro = getDownCollapsedAlphaPro;

alphaCollapsedContentByPro(downCollapsedAlphaPro);

//按照downShopBarTransPro,竖立购物内容内容位移

floatdownShopBarTransPro = getDownShopBarTransPro;

transShopBarByPro(downShopBarTransPro);

//按照upCollapsedContentTransPro,竖立折叠内容位移

floatupCollapsedContentTransPro = getUpCollapsedContentTransPro;

transCollapsedContentByPro(upCollapsedContentTransPro);

if(mProgressUpdateListener!= null){

mProgressUpdateListener.onUpAlphaScaleProUpdate(upAlphaScalePro);

mProgressUpdateListener.onUpAlphaGradientProUpdate(upAlphaGradientPro);

mProgressUpdateListener.onDownCollapsedAlphaProUpdate(downCollapsedAlphaPro);

mProgressUpdateListener.onDownShopBarTransProUpdate(downShopBarTransPro);

mProgressUpdateListener.onUpCollapsedContentTransProUpdate(upCollapsedContentTransPro);

}

}

});

reboundedAnim.setDuration(ANIM_DURATION_FRACTION);

}

@Override

publicbooleanonNestedFling(@NonNull View target, floatvelocityX, floatvelocityY, booleanconsumed) {

returnfalse;

}

@Override

publicbooleanonNestedPreFling(@NonNull View target, floatvelocityX, floatvelocityY) {

if(velocityY< 0){ //去下滑动的惯性滑动

floattranslationY = mLlContent.getTranslationY;

floatdy = translationY - velocityY;

if(translationY >topBarHeight && translationY<= downFlingCutOffY) { //非折叠状态

//按照dy竖立动画终结值,只有dy>contentTransY才会有回弹,downFlingCutOffY是回弹的最大值

if(dy<=contentTransY){

reboundedAnim.setFloatValues(translationY,dy);

} elseif(dy>contentTransY&&dy<downFlingCutOffY){

reboundedAnim.setFloatValues(translationY,dy,contentTransY);

} else{

reboundedAnim.setFloatValues(translationY,downFlingCutOffY,contentTransY);

}

reboundedAnim.start;

returntrue;

}

}

returnfalse;

}

开释资源

在View从Window上移除时候,实走要停留动画、开释监听者的操作。

@Override

protectedvoidonDetachedFromWindow{

super.onDetachedFromWindow;

if(restoreOrExpandAnimator.isStarted) {

restoreOrExpandAnimator.cancel;

restoreOrExpandAnimator.removeAllUpdateListeners;

restoreOrExpandAnimator = null;

}

if(reboundedAnim.isStarted){

reboundedAnim.cancel;

reboundedAnim.removeAllUpdateListeners;

reboundedAnim = null;

}

if(mProgressUpdateListener!= null){

mProgressUpdateListener= null;

}

}

3

商家点餐页的嵌套滑动实现

终局分析

商家点餐页组织主要有上图五片面构成,逻辑上的层级如图所示有3层。

滑动Content片面时行使View的TransitionY属性转折位置来消耗滑动值,按照Content片面的TransitionY设定各栽周围从而计算百分最近实走位移、Alpha终局。

下面来表明上图中变量的意义:

contentTransY; //滑动内容初首化TransY

iconTransY; //分享、关闭icon初首化transY

upEndIconTransY; //分享、关闭icon上滑最后transY

downFlingCutOffY; ///从打开状态下滑产生fling时回弹到初首状态的最大值

4

ElemeFoodNestedScrollLayout

商家点餐页的嵌套滑的自定义View继承FrameLayout、实现NestedScrollingParent2接口,命名为ElemeFoodNestedScrollLayout。

组织

下面是组织要点,偏重于控件的尺寸和位置,完善组织请参考:ElemeFoodNestedScrollLayout组织

https://github.com/pengguanming/ElemeNestedScrolling/blob/master/app/src/main/res/layout/activity_my_eleme_layout.xml

< ElemeFoodNestedScrollLayout

android:layout_width= "match_parent"

android:layout_height= "match_parent">

<!--Mask片面-->

< View

android:layout_width= "match_parent"

android:layout_height= "match_parent"/>

<!--Mask片面-->

<!--Content片面-->

< android.support.v4.widget.NestedScrollView

android:id= "@ id/ns"

android:translationY= "@dimen/food_content_trans_y"

android:fillViewport= "true"

android:background= "@android:color/white"

android:layout_width= "match_parent"

android:layout_height= "match_parent"

...

>

<!--Content片面-->

<!--Icon片面-->

< ImageView

android:layout_gravity= "right"

android:layout_width= "28dp"

android:layout_height= "28dp"

android:translationY= "@dimen/iv_food_icon_trans_y"

android:layout_marginEnd= "60dp"

/>

< ImageView

android:layout_gravity= "right"

android:layout_width= "28dp"

android:layout_height= "28dp"

android:translationY= "@dimen/iv_food_icon_trans_y"

android:layout_marginEnd= "16dp"

/>

<!--Icon片面-->

<!--Expand片面-->

< ImageView

android:alpha= "0"

android:layout_gravity= "bottom"

android:layout_width= "match_parent"

android:layout_height= "@dimen/iv_food_expand"

/>

<!--Expand片面-->

<!--Shop Bar片面-->

< android.support.constraint.ConstraintLayout

android:layout_width= "match_parent"

android:layout_gravity= "bottom"

android:layout_height= "@dimen/shop_bar_height"

android:translationY= "@dimen/shop_bar_height"

...

>

<!--Shop Bar片面-->

</ ElemeFoodNestedScrollLayout>

绑定必要做终局的View、引入Dimens、竖立Content片面的初首化TransitionY

从上面图片能够分析出:

关闭状态时,Content片面的TransY为满屏高度

publicclassElemeFoodNestedScrollLayoutextendsFrameLayoutimplementsNestedScrollingParent2{

...

//shopBar片面

privateView mShopBar;

//content片面

privateView mNestedScrollView;

privateView mTvComm;

privateView mTvGoodCommRate;

privateView mTvCommDetail;

privateView mTvCommCount;

privateView mVLine;

privateView mTvFoodDetail;

//expand片面

privateView mIvExpand;

//icon片面

privateView mIvShare;

privateView mIvClose;

//mask片面

privateView mVMask;

privatefloatshopBarHeight; //shopBar片面高度

privatefloativExpandHegiht; //ivExpand片面高度

privatefloatstatusBarHeight; //状态栏高度

privatefloaticonTransY; //分享、关闭icon初首化transY

privatefloatcontentTransY; //滑动内容初首化TransY

privatefloatdownFlingCutOffY; //下滑时fling上片面回弹临界值

privatefloatupEndIconTransY; //分享、关闭icon上滑最后transY

@Override

protectedvoidonSizeChanged( intw, inth, intoldw, intoldh) {

super.onSizeChanged(w, h, oldw, oldh);

shopBarHeight = getResources.getDimension(R.dimen.shop_bar_height);

ivExpandHegiht = getResources.getDimension(R.dimen.iv_food_expand);

contentTransY = getResources.getDimension(R.dimen.food_content_trans_y);

iconTransY = getResources.getDimension(R.dimen.iv_food_icon_trans_y);

statusBarHeight = getResources.getDimensionPixelSize(getResources.getIdentifier( "status_bar_height", "dimen", "android"));

downFlingCutOffY = contentTransY dp2px( 92);

upEndIconTransY = statusBarHeight dp2px( 8);

//由于最先就是关闭状态,竖立Content片面的TransY为满屏高度

mNestedScrollView.setTranslationY(getMeasuredHeight);

}

@Override

protectedvoidonFinishInflate{

super.onFinishInflate;

mNestedScrollView = findViewById(R.id.ns);

mShopBar = findViewById(R.id.cl_food_shop_bar);

mTvComm = findViewById(R.id.t_comm);

mTvGoodCommRate = findViewById(R.id.t_good_comm_rate);

mTvCommDetail = findViewById(R.id.t_comm_detail);

mTvFoodDetail = findViewById(R.id.t_food_detail);

mTvCommCount = findViewById(R.id.t_comm_count);

mVLine = findViewById(R.id.view_line);

mIvExpand = findViewById(R.id.iv_food_expand);

mIvShare = findViewById(R.id.iv_small_share);

mIvClose = findViewById(R.id.iv_small_close);

mVMask = findViewById(R.id.v_mask);

}

}

实现NestedScrollingParent2接口

onStartNestedScroll

ElemeFoodNestedScrollLayout只处理Content片面里可滑动View的垂直倾向的滑动。

@Override

publicbooleanonStartNestedScroll(@NonNull View child, @NonNull View target, intaxes, inttype) {

//处理Content片面里可滑动View的垂直倾向的滑动

returnaxes == ViewCompat.SCROLL_AXIS_VERTICAL && target.getId == R.id.ns;

}

onNestedPreScroll

接下来就是处理滑动,上面终局分析挑过:Content片面的上滑周围=[0,contentTransY]、

下滑周围=[contentTransY,即满屏高度]即滑动周围为[0,即满屏高度],ElemeFoodNestedScrollLayout要限制Content片面的TransitionY值要在周围内,之前的商家页已经说过了仔细思路,这边不再赘述。

...

privateProgressUpdateListener mProgressUpdateListener;

@Override

publicvoidonNestedPreScroll(@NonNull View target, intdx, intdy, @NonNull int[] consumed, inttype) {

floatcontentTransY = target.getTranslationY - dy;

//处理上滑

if(dy > 0) {

if(contentTransY >= 0) {

translationByConsume(target, contentTransY, consumed, dy);

} else{

translationByConsume(target, 0, consumed, (target.getTranslationY - 0));

}

}

//处理下滑

if(dy < 0&& !target.canScrollVertically(- 1)) {

if(contentTransY >= 0&& contentTransY < getMeasuredHeight) {

translationByConsume(target, contentTransY, consumed, dy);

} else{

translationByConsume(target, getMeasuredHeight, consumed, getMeasuredHeight - target.getTranslationY);

}

}

alphaTransView(contentTransY);

if(mProgressUpdateListener!= null){

mProgressUpdateListener.onDownConetntCloseProUpdate(getDownConetntClosePro);

}

}

privatevoidalphaTransView( floattransY) {

floatupCollapseTransPro = getUpExpandTransPro;

//位移购物内容

floatshopBarTransY = ( 1- upCollapseTransPro) * shopBarHeight;

translation(mShopBar, shopBarTransY);

//竖立商品新闻View的透明度转折

setAlpha(mTvComm, upCollapseTransPro);

setAlpha(mTvGoodCommRate, upCollapseTransPro);

setAlpha(mTvCommDetail, upCollapseTransPro);

setAlpha(mTvFoodDetail, upCollapseTransPro);

setAlpha(mTvCommCount, 1- upCollapseTransPro);

setAlpha(mVLine, 1- upCollapseTransPro);

//位移share close两个Icon,竖立打开icon透明度

if(transY <= contentTransY) {

floativExpandUpTransY = upCollapseTransPro * -contentTransY;

translation(mIvExpand, ivExpandUpTransY);

setAlpha(mIvExpand, 1- upCollapseTransPro);

floaticonTransY = upEndIconTransY ( 1- upCollapseTransPro) * ( this.iconTransY - upEndIconTransY);

translation(mIvShare, iconTransY);

translation(mIvClose, iconTransY);

} elseif(transY > contentTransY && transY <= getMeasuredHeight) {

floativExpandDowndTransY = ( 1- getDownIvExpnadPro) * ivExpandHegiht;

translation(mIvExpand, ivExpandDowndTransY);

floaticonTransY = transY dp2px( 16);

translation(mIvShare, iconTransY);

translation(mIvClose, iconTransY);

}

}

privatefloatgetDownConetntClosePro{

return(mNestedScrollView.getTranslationY - contentTransY) / (getMeasuredHeight - contentTransY);

}

privatefloatgetDownIvExpnadPro{

return((contentTransY ivExpandHegiht)-MathUtils.clamp(mNestedScrollView.getTranslationY, contentTransY, contentTransY ivExpandHegiht)) / ivExpandHegiht;

}

privatefloatgetUpExpandTransPro{

return(contentTransY - MathUtils.clamp(mNestedScrollView.getTranslationY, 0, contentTransY)) / contentTransY;

}

publicinterfaceProgressUpdateListener{

voidonDownConetntCloseProUpdate( floatpro) ;

}

onStopNestedScroll

在从初首状态到打开状态的上滑过程中松手,若上滑百分比幼于等于50%则实走恢复到初首状态的动画,否则实走转化到打开状态的动画;同理从初首状态到关闭状态下滑过程中松手,若下滑百分比幼于等于50%则实走恢复到初首状态的动画,否则实走转化到关闭状态的动画;

...

privateValueAnimator restoreOrExpandOrCloseAnimator; //收首或打开折叠内容时实走的动画

privateNestedScrollingParentHelper mParentHelper;

publicElemeFoodNestedScrollLayout(@NonNull Context context, @Nullable AttributeSet attrs, intdefStyleAttr) {

super(context, attrs, defStyleAttr);

mParentHelper = newNestedScrollingParentHelper( this);

...

restoreOrExpandOrCloseAnimator = newValueAnimator;

restoreOrExpandOrCloseAnimator.setInterpolator( newAccelerateInterpolator);

restoreOrExpandOrCloseAnimator.addUpdateListener( newValueAnimator.AnimatorUpdateListener {

@Override

publicvoidonAnimationUpdate(ValueAnimator animation){

translation(mNestedScrollView, ( float) animation.getAnimatedValue);

alphaTransView(mNestedScrollView.getTranslationY);

if(mProgressUpdateListener!= null){

mProgressUpdateListener.onDownConetntCloseProUpdate(getDownConetntClosePro);

}

}

});

restoreOrExpandOrCloseAnimator.addListener( newAnimatorListenerAdapter {

@Override

publicvoidonAnimationEnd(Animator animation){

intalpha=mNestedScrollView.getTranslationY >= getMeasuredHeight? 0: 1;

setAlpha(mIvClose,alpha);

setAlpha(mIvShare,alpha);

}

});

}

@Override

publicvoidonStopNestedScroll(@NonNull View target, inttype) {

mParentHelper.onStopNestedScroll(target, type);

floattranslationY = target.getTranslationY;

if(translationY == contentTransY

原标题:5G视频手机双路防抖,给你稳稳的幸福!

中国网6月12日讯 据黑龙江省纪委监委消息,黑龙江省人民防空办公室党组成员、副主任高峰涉嫌严重违纪违法,目前正接受纪律审查和监察调查。

新京报讯(记者 滕朝)6月10日,电影《乱世佳人》因“描述种族主义”被华纳传媒的HBO Max下架,不过该片下架一天后,迅速登上了亚马逊平台影片DVD销量榜榜首。该网站目前提供《乱世佳人》70周年纪念双碟DVD版,起价29.55美元,高清数字电影租赁价格为3.99美元,购买价格为9.99美元。

@中国篮球协会 6月10日宣布,取消2019-2020赛季WCBA联赛后续比赛。

原标题:一条草鱼,一个番茄,锅里一煮,鱼肉滑嫩,吃完回味无穷!

posted @ 20-06-26 05:21  作者:admin  阅读量:

Powered by 北京快三 @2018 RSS地图 html地图

追求更好 技术支持