9.Android RecyclerView 画廊 自定义LayoutManager实现弧形滚动菜单

今天开始讲RecycleView的系列教程。分割线,分组,局部刷新,动态添加,缓存原理,抖音效果,瀑布流。嵌套,动画等等

从月薪3000到年薪60万。从专科生到深圳一线大厂。关注我就能达到大师级水平,这话我终于敢说了, 年薪60万不是梦!

请问:简书怎么可以把代码格式调整?我贴出来换格式了。你们直接去Github下载工程!



功能:

支持无限循环

支持画廊效果

支持中间放大并定格

支持方向切换

支持回弹效果

效果分析

1.优先实现圆弧效果

2.然后实现放大效果

3.实现定格效果

原理:使用recyclerview的话你只需通过setAdapter方法改变数据,setLayoutManager方法改变样式即可,

重写LayoutManager。我们自定义ViewPagerLayoutManager extends LinearLayoutManager 然后二次封装:

/**

* 可缩放的弧形菜单

*/

public class CircleScaleLayoutManagerextends ViewPagerLayoutManager {

圆弧效果

给每个item设置旋转角度

itemView.setRotation(targetOffset);

@Override

protected void setItemViewProperty(View itemView, float targetOffset) {

switch (gravity) {

case RIGHT:

case TOP:

if (flipRotate) {

itemView.setRotation(targetOffset);

            }else {

itemView.setRotation(360 - targetOffset);

            }

break;

        case LEFT:

case BOTTOM:

default:

if (flipRotate) {

itemView.setRotation(360 - targetOffset);

            }else {

itemView.setRotation(targetOffset);

            }

break;

    }

}

然后实现放大效果 itemView.setScaleX(scale);

@Override

protected void setItemViewProperty(View itemView, float targetOffset) {

float scale =1f;

    switch (gravity) {

case RIGHT:

case TOP:

if (flipRotate) {

itemView.setRotation(targetOffset);

                if (targetOffset -angleInterval) {

float diff = Math.abs(Math.abs(itemView.getRotation() -angleInterval) -angleInterval);

                    scale = (centerScale -1f) / -angleInterval * diff +centerScale;

                }

}else {

itemView.setRotation(360 - targetOffset);

                if (targetOffset -angleInterval) {

float diff = Math.abs(Math.abs(360 - itemView.getRotation() -angleInterval) -angleInterval);

                    scale = (centerScale -1f) / -angleInterval * diff +centerScale;

                }

}

break;

        case LEFT:

case BOTTOM:

default:

if (flipRotate) {

itemView.setRotation(360 - targetOffset);

                if (targetOffset -angleInterval) {

float diff = Math.abs(Math.abs(360 - itemView.getRotation() -angleInterval) -angleInterval);

                    scale = (centerScale -1f) / -angleInterval * diff +centerScale;

                }

}else {

itemView.setRotation(targetOffset);

                if (targetOffset -angleInterval) {

float diff = Math.abs(Math.abs(itemView.getRotation() -angleInterval) -angleInterval);

                    scale = (centerScale -1f) / -angleInterval * diff +centerScale;

                }

}

break;

    }

itemView.setScaleX(scale);

    itemView.setScaleY(scale);

}

3.实现定格效果:通过监听RecyclerView的滚动方法

private final RecyclerView.OnScrollListenermScrollListener =

new RecyclerView.OnScrollListener() {

boolean mScrolled =false;

            @Override

            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

super.onScrollStateChanged(recyclerView, newState);

                final ViewPagerLayoutManager layoutManager =

(ViewPagerLayoutManager) recyclerView.getLayoutManager();

                final ViewPagerLayoutManager.OnPageChangeListener onPageChangeListener =

layoutManager.onPageChangeListener;

                if (onPageChangeListener !=null) {

onPageChangeListener.onPageScrollStateChanged(newState);

                }

if (newState == RecyclerView.SCROLL_STATE_IDLE &&mScrolled) {

mScrolled =false;

                    if (!snapToCenter) {

snapToCenter =true;

                        snapToCenterView(layoutManager, onPageChangeListener);

                    }else {

snapToCenter =false;

                    }

}

}

@Override

            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

if (dx !=0 || dy !=0) {

mScrolled =true;

                }

}

};

@Override

public boolean onFling(int velocityX, int velocityY) {

ViewPagerLayoutManager layoutManager = (ViewPagerLayoutManager)mRecyclerView.getLayoutManager();

    if (layoutManager ==null) {

return false;

    }

RecyclerView.Adapter adapter =mRecyclerView.getAdapter();

    if (adapter ==null) {

return false;

    }

if (!layoutManager.getInfinite() &&

(layoutManager.mOffset == layoutManager.getMaxOffset()

|| layoutManager.mOffset == layoutManager.getMinOffset())) {

return false;

    }

final int minFlingVelocity =mRecyclerView.getMinFlingVelocity();

    mGravityScroller.fling(0, 0, velocityX, velocityY,

            Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);

    if (layoutManager.mOrientation == ViewPagerLayoutManager.VERTICAL

            && Math.abs(velocityY) > minFlingVelocity) {

final int currentPosition = layoutManager.getCurrentPositionOffset();

        final int offsetPosition = (int) (mGravityScroller.getFinalY() /

layoutManager.mInterval / layoutManager.getDistanceRatio());

        ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?

-currentPosition - offsetPosition : currentPosition + offsetPosition);

return true;

    }else if (layoutManager.mOrientation == ViewPagerLayoutManager.HORIZONTAL

            && Math.abs(velocityX) > minFlingVelocity) {

final int currentPosition = layoutManager.getCurrentPositionOffset();

        final int offsetPosition = (int) (mGravityScroller.getFinalX() /

layoutManager.mInterval / layoutManager.getDistanceRatio());

        ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?

-currentPosition - offsetPosition : currentPosition + offsetPosition);

return true;

    }

return true;

}


源码:可以看我的上一篇,模仿抖音的效果!!!!!

public class ScrollHelper {

/* package */ static void smoothScrollToPosition(RecyclerView recyclerView, ViewPagerLayoutManager viewPagerLayoutManager, int targetPosition) {

final int delta = viewPagerLayoutManager.getOffsetToPosition(targetPosition);

        if (viewPagerLayoutManager.getOrientation() == ViewPagerLayoutManager.VERTICAL) {

recyclerView.smoothScrollBy(0, delta);

        }else {

recyclerView.smoothScrollBy(delta, 0);

        }

}

public static void smoothScrollToTargetView(RecyclerView recyclerView, View targetView) {

final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

        if (!(layoutManagerinstanceof ViewPagerLayoutManager))return;

        final int targetPosition = ((ViewPagerLayoutManager) layoutManager).getLayoutPositionOfView(targetView);

        smoothScrollToPosition(recyclerView, (ViewPagerLayoutManager) layoutManager, targetPosition);

    }

}


/**

* 可缩放的弧形菜单

*/

publicclassCircleScaleLayoutManagerextendsViewPagerLayoutManager{

publicstaticfinalintLEFT=10;

publicstaticfinalintRIGHT=11;

publicstaticfinalintTOP=12;

publicstaticfinalintBOTTOM=13;

publicstaticfinalintLEFT_ON_TOP=4;

publicstaticfinalintRIGHT_ON_TOP=5;

publicstaticfinalintCENTER_ON_TOP=6;

privateintradius;// 半径

privateintangleInterval;// 间隔角度

privatefloatmoveSpeed;

privatefloatcenterScale;// 中心缩放比

privatefloatmaxRemoveAngle;

privatefloatminRemoveAngle;

privateintgravity;

privatebooleanflipRotate;

privateintzAlignment;

publicCircleScaleLayoutManager(Contextcontext) {

this(newBuilder(context));

    }

publicCircleScaleLayoutManager(Contextcontext,intgravity,booleanreverseLayout) {

this(newBuilder(context).setGravity(gravity).setReverseLayout(reverseLayout));

    }

publicCircleScaleLayoutManager(Contextcontext,booleanreverseLayout) {

this(newBuilder(context).setReverseLayout(reverseLayout));

    }

publicCircleScaleLayoutManager(Builderbuilder) {

this(builder.context,builder.radius,builder.angleInterval,builder.centerScale,builder.moveSpeed,

builder.maxRemoveAngle,builder.minRemoveAngle,builder.gravity,builder.zAlignment,

builder.flipRotate,builder.maxVisibleItemCount,builder.distanceToBottom,builder.reverseLayout);

    }

privateCircleScaleLayoutManager(Contextcontext,intradius,intangleInterval,floatcenterScale,

floatmoveSpeed,floatmax,floatmin,intgravity,intzAlignment,

booleanflipRotate,intmaxVisibleItemCount,intdistanceToBottom,booleanreverseLayout) {

super(context,HORIZONTAL,reverseLayout);

setEnableBringCenterToFront(true);

setMaxVisibleItemCount(maxVisibleItemCount);

setDistanceToBottom(distanceToBottom);

this.radius=radius;

this.angleInterval=angleInterval;

this.centerScale=centerScale;

this.moveSpeed=moveSpeed;

this.maxRemoveAngle=max;

this.minRemoveAngle=min;

this.gravity=gravity;

this.flipRotate=flipRotate;

this.zAlignment=zAlignment;

    }

publicintgetRadius() {

returnradius;

    }

publicintgetAngleInterval() {

returnangleInterval;

    }

publicfloatgetCenterScale() {

returncenterScale;

    }

publicfloatgetMoveSpeed() {

returnmoveSpeed;

    }

publicfloatgetMaxRemoveAngle() {

returnmaxRemoveAngle;

    }

publicfloatgetMinRemoveAngle() {

returnminRemoveAngle;

    }

publicintgetGravity() {

returngravity;

    }

publicbooleangetFlipRotate() {

returnflipRotate;

    }

publicintgetZAlignment() {

returnzAlignment;

    }

publicvoidsetRadius(intradius) {

assertNotInLayoutOrScroll(null);

if(this.radius==radius)return;

this.radius=radius;

removeAllViews();

    }

publicvoidsetAngleInterval(intangleInterval) {

assertNotInLayoutOrScroll(null);

if(this.angleInterval==angleInterval)return;

this.angleInterval=angleInterval;

removeAllViews();

    }

publicvoidsetCenterScale(floatcenterScale) {

assertNotInLayoutOrScroll(null);

if(this.centerScale==centerScale)return;

this.centerScale=centerScale;

requestLayout();

    }

publicvoidsetMoveSpeed(floatmoveSpeed) {

assertNotInLayoutOrScroll(null);

if(this.moveSpeed==moveSpeed)return;

this.moveSpeed=moveSpeed;

    }

publicvoidsetMaxRemoveAngle(floatmaxRemoveAngle) {

assertNotInLayoutOrScroll(null);

if(this.maxRemoveAngle==maxRemoveAngle)return;

this.maxRemoveAngle=maxRemoveAngle;

requestLayout();

    }

publicvoidsetMinRemoveAngle(floatminRemoveAngle) {

assertNotInLayoutOrScroll(null);

if(this.minRemoveAngle==minRemoveAngle)return;

this.minRemoveAngle=minRemoveAngle;

requestLayout();

    }

@SuppressLint("WrongConstant")

publicvoidsetGravity(intgravity) {

assertNotInLayoutOrScroll(null);

assertGravity(gravity);

if(this.gravity==gravity)return;

this.gravity=gravity;

if(gravity==LEFT||gravity==RIGHT) {

setOrientation(VERTICAL);

}else{

setOrientation(HORIZONTAL);

        }

requestLayout();

    }

publicvoidsetFlipRotate(booleanflipRotate) {

assertNotInLayoutOrScroll(null);

if(this.flipRotate==flipRotate)return;

this.flipRotate=flipRotate;

requestLayout();

    }

publicvoidsetZAlignment(intzAlignment) {

assertNotInLayoutOrScroll(null);

assertZAlignmentState(zAlignment);

if(this.zAlignment==zAlignment)return;

this.zAlignment=zAlignment;

requestLayout();

    }

@Override

protectedfloatsetInterval() {

returnangleInterval;

    }

@Override

protectedvoidsetUp() {

radius=radius==Builder.INVALID_VALUE?mDecoratedMeasurementInOther:radius;

    }

@Override

protectedfloatmaxRemoveOffset() {

returnmaxRemoveAngle;

    }

@Override

protectedfloatminRemoveOffset() {

returnminRemoveAngle;

    }

@Override

protectedintcalItemLeft(ViewitemView,floattargetOffset) {

doublesin=Math.sin(Math.toRadians(90-targetOffset));

doublecos=Math.cos(Math.toRadians(90-targetOffset));

switch(gravity) {

caseLEFT:

return(int) (radius*sin-radius);

caseRIGHT:

return(int) (radius-radius*sin);

caseTOP:

caseBOTTOM:

default:

return(int) (radius*cos);

        }

    }

@Override

protectedintcalItemTop(ViewitemView,floattargetOffset) {

doublesin=Math.sin(Math.toRadians(90-targetOffset));

doublecos=Math.cos(Math.toRadians(90-targetOffset));

switch(gravity) {

caseLEFT:

caseRIGHT:

return(int) (radius*cos);

caseTOP:

return(int) (radius*sin-radius);

caseBOTTOM:

default:

return(int) (radius-radius*sin);

        }

    }

@Override

protectedvoidsetItemViewProperty(ViewitemView,floattargetOffset) {

floatscale=1f;

switch(gravity) {

caseRIGHT:

caseTOP:

// 图片平行 渐变缩放

if(targetOffset<angleInterval&&targetOffset>-angleInterval) {

floatdiff=Math.abs(targetOffset);

scale=diff*(centerScale-1f)/-angleInterval+centerScale;

// 增加itemView回调

onPageChangeListener.onViewCentered(diff<5,itemView);

                }

break;

caseLEFT:

caseBOTTOM:

default:

if(flipRotate) {

itemView.setRotation(360-targetOffset);

if(targetOffset<angleInterval&&targetOffset>-angleInterval) {

floatdiff=Math.abs(Math.abs(360-itemView.getRotation()-angleInterval)-angleInterval);

scale=(centerScale-1f)/-angleInterval*diff+centerScale;

                    }

}else{

itemView.setRotation(targetOffset);

if(targetOffset<angleInterval&&targetOffset>-angleInterval) {

floatdiff=Math.abs(Math.abs(itemView.getRotation()-angleInterval)-angleInterval);

scale=(centerScale-1f)/-angleInterval*diff+centerScale;

                    }

                }

break;

        }

itemView.setScaleX(scale);

itemView.setScaleY(scale);

    }

@Override

protectedfloatsetViewElevation(ViewitemView,floattargetOffset) {

if(zAlignment==LEFT_ON_TOP)

return(540-targetOffset)/72;

elseif(zAlignment==RIGHT_ON_TOP)

return(targetOffset-540)/72;

else

return(360-Math.abs(targetOffset))/72;

    }

@Override

protectedfloatgetDistanceRatio() {

if(moveSpeed==0)returnFloat.MAX_VALUE;

return1/moveSpeed;

    }

privatestaticvoidassertGravity(intgravity) {

if(gravity!=LEFT&&gravity!=RIGHT&&gravity!=TOP&&gravity!=BOTTOM) {

thrownewIllegalArgumentException("gravity must be one of LEFT RIGHT TOP and BOTTOM");

        }

    }

privatestaticvoidassertZAlignmentState(intzAlignment) {

if(zAlignment!=LEFT_ON_TOP&&zAlignment!=RIGHT_ON_TOP&&zAlignment!=CENTER_ON_TOP) {

thrownewIllegalArgumentException("zAlignment must be one of LEFT_ON_TOP RIGHT_ON_TOP and CENTER_ON_TOP");

        }

    }

publicstaticclassBuilder{

privatestaticintINTERVAL_ANGLE=30;// The default mInterval angle between each items

privatestaticfloatDISTANCE_RATIO=10f;// Finger swipe distance divide item rotate angle

privatestaticfinalfloatSCALE_RATE=1.2f;

privatestaticintINVALID_VALUE=Integer.MIN_VALUE;

privateintradius;

privateintangleInterval;

privatefloatcenterScale;

privatefloatmoveSpeed;

privatefloatmaxRemoveAngle;

privatefloatminRemoveAngle;

privatebooleanreverseLayout;

privateContextcontext;

privateintgravity;

privatebooleanflipRotate;

privateintzAlignment;

privateintmaxVisibleItemCount;

privateintdistanceToBottom;

publicBuilder(Contextcontext) {

this.context=context;

radius=INVALID_VALUE;

angleInterval=INTERVAL_ANGLE;

centerScale=SCALE_RATE;

moveSpeed=1/DISTANCE_RATIO;

maxRemoveAngle=90;

minRemoveAngle=-90;

reverseLayout=false;

flipRotate=false;

gravity=BOTTOM;

zAlignment=CENTER_ON_TOP;

distanceToBottom=ViewPagerLayoutManager.INVALID_SIZE;

maxVisibleItemCount=ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;

        }

publicBuildersetRadius(intradius) {

this.radius=radius;

returnthis;

        }

publicBuildersetAngleInterval(intangleInterval) {

this.angleInterval=angleInterval;

returnthis;

        }

publicBuildersetCenterScale(floatcenterScale) {

this.centerScale=centerScale;

returnthis;

        }

publicBuildersetMoveSpeed(intmoveSpeed) {

this.moveSpeed=moveSpeed;

returnthis;

        }

publicBuildersetMaxRemoveAngle(floatmaxRemoveAngle) {

this.maxRemoveAngle=maxRemoveAngle;

returnthis;

        }

publicBuildersetMinRemoveAngle(floatminRemoveAngle) {

this.minRemoveAngle=minRemoveAngle;

returnthis;

        }

publicBuildersetReverseLayout(booleanreverseLayout) {

this.reverseLayout=reverseLayout;

returnthis;

        }

publicBuildersetGravity(intgravity) {

assertGravity(gravity);

this.gravity=gravity;

returnthis;

        }

publicBuildersetFlipRotate(booleanflipRotate) {

this.flipRotate=flipRotate;

returnthis;

        }

publicBuildersetZAlignment(intzAlignment) {

assertZAlignmentState(zAlignment);

this.zAlignment=zAlignment;

returnthis;

        }

publicBuildersetMaxVisibleItemCount(intmaxVisibleItemCount) {

this.maxVisibleItemCount=maxVisibleItemCount;

returnthis;

        }

publicBuildersetDistanceToBottom(intdistanceToBottom) {

this.distanceToBottom=distanceToBottom;

returnthis;

        }

publicCircleScaleLayoutManagerbuild() {

returnnewCircleScaleLayoutManager(this);

        }

    }

}

public static final int DETERMINE_BY_MAX_AND_MIN = -1;

    public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;

    public static final int VERTICAL = OrientationHelper.VERTICAL;

    private static final int DIRECTION_NO_WHERE = -1;

    private static final int DIRECTION_FORWARD =0;

    private static final int DIRECTION_BACKWARD =1;

    protected static final int INVALID_SIZE = Integer.MAX_VALUE;

    private SparseArraypositionCache =new SparseArray<>();

    protected int mDecoratedMeasurement;

    protected int mDecoratedMeasurementInOther;



demo地址:https://github.com/pengcaihua123456/shennandadao

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