RecyclerView 自定義對齊方式合愈,橫向滑動

RecyclerView 自定義對齊方式,本文講的是橫向滑動击狮,依靠左邊對齊佛析。

滑動后,手指松開彪蓬。不管向左還是向右说莫,滑動一點,不超過itemView寬的一半寞焙,就反彈回去。如果超過了一半,加速滑動到整個itemView的寬捣郊。

new LeftSnapHelper().attachToRecyclerView(recyclerView)辽狈。


import android.graphics.PointF;

import android.util.DisplayMetrics;

import android.util.Log;

import android.view.View;

import androidx.annotation.NonNull;

import androidx.annotation.Nullable;

import androidx.recyclerview.widget.LinearSmoothScroller;

import androidx.recyclerview.widget.OrientationHelper;

import androidx.recyclerview.widget.RecyclerView;

import androidx.recyclerview.widget.SnapHelper;

public class LeftSnapHelperextends SnapHelper {

private static final float INVALID_DISTANCE =1f;

? ? //數(shù)值越小,速度越慢

? ? private static final float MILLISECONDS_PER_INCH =40f;

? ? private OrientationHelper mHorizontalHelper;

? ? private RecyclerView mRecyclerView;

? ? @Override

public void attachToRecyclerView(@Nullable RecyclerView recyclerView)throws IllegalStateException {

super.attachToRecyclerView(recyclerView);

? ? ? ? //如果SnapHelper之前已經(jīng)附著到此RecyclerView上呛牲,不用進行任何操作

? ? ? ? if (mRecyclerView == recyclerView) {

return;

? ? ? ? }

//更新RecyclerView對象引用

? ? ? ? mRecyclerView = recyclerView;

? ? ? ? //如果SnapHelper之前附著的RecyclerView和現(xiàn)在的不一致刮萌,清理掉之前RecyclerView的回調(diào)

? ? ? ? if(mRecyclerView !=null){

//設(shè)置當(dāng)前RecyclerView對象的回調(diào)

//? ? ? ? ? ? setupCallbacks();

? ? ? ? ? ? mRecyclerView.setOnFlingListener(this);

? ? ? ? ? ? //調(diào)用snapToTargetExistingView()方法以實現(xiàn)對SnapView的對齊滾動處理

? ? ? ? ? ? snapToTargetExistingView();

? ? ? ? }

}

@Nullable

@Override

protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {

if (!(layoutManagerinstanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {

return null;

? ? ? ? }

LinearSmoothScroller smoothScroller =new LinearSmoothScroller(mRecyclerView.getContext()){

@Override

protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {

int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView);

? ? ? ? ? ? ? ? final int dx = snapDistances[0];

? ? ? ? ? ? ? ? final int dy = snapDistances[1];

? ? ? ? ? ? ? ? final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));

? ? ? ? ? ? ? ? if (time >0) {

action.update(dx, dy, time, mDecelerateInterpolator);

? ? ? ? ? ? ? ? }

}

@Override

protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {

return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;

? ? ? ? ? ? }

};

? ? ? ? return smoothScroller;

? ? }

private void snapToTargetExistingView() {

if (mRecyclerView ==null) {

return;

? ? ? ? }

RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();

? ? ? ? if (layoutManager ==null) {

return;

? ? ? ? }

//找出SnapView

? ? ? ? View snapView = findSnapView(layoutManager);

? ? ? ? if (snapView ==null) {

return;

? ? ? ? }

//計算出SnapView需要滾動的距離

? ? ? ? int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);

? ? ? ? //如果需要滾動的距離不是為0,就調(diào)用smoothScrollBy()使RecyclerView滾動相應(yīng)的距離

? ? ? ? if (snapDistance[0] !=0 || snapDistance[1] !=0) {

mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);

? ? ? ? }

}

@Override

public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {

int[] out =new int[2];

? ? ? ? if (layoutManager.canScrollHorizontally()) {

out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager));

? ? ? ? }else {

out[0] =0;

? ? ? ? }

return out;

? ? }

@Override

public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {

//判斷l(xiāng)ayoutManager是否實現(xiàn)了RecyclerView.SmoothScroller.ScrollVectorProvider這個接口

? ? ? ? if (!(layoutManagerinstanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {

return RecyclerView.NO_POSITION;

? ? ? ? }

final int itemCount = layoutManager.getItemCount();

? ? ? ? if (itemCount ==0) {

return RecyclerView.NO_POSITION;

? ? ? ? }

//找到snapView

? ? ? ? final View currentView = findSnapView(layoutManager);

? ? ? ? if (currentView ==null) {

return RecyclerView.NO_POSITION;

? ? ? ? }

final int currentPosition = layoutManager.getPosition(currentView);

? ? ? ? if (currentPosition == RecyclerView.NO_POSITION) {

return RecyclerView.NO_POSITION;

? ? ? ? }

RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager;

? ? ? ? // 通過ScrollVectorProvider接口中的computeScrollVectorForPosition()方法

? ? ? ? // 來確定layoutManager的布局方向

? ? ? ? PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount -1);

? ? ? ? if (vectorForEnd ==null) {

return RecyclerView.NO_POSITION;

? ? ? ? }

int vDeltaJump=0, hDeltaJump;

? ? ? ? //橫向

? ? ? ? if (layoutManager.canScrollHorizontally()) {

//layoutManager是橫向布局娘扩,并且內(nèi)容超出一屏着茸,canScrollHorizontally()才返回true

? ? ? ? ? ? //估算fling結(jié)束時相對于當(dāng)前snapView位置的橫向位置偏移量

? ? ? ? ? ? hDeltaJump = estimateNextPositionDiffForFling(layoutManager, getHorizontalHelper(layoutManager), velocityX, 0);

? ? ? ? ? ? //vectorForEnd.x < 0代表layoutManager是反向布局的,就把偏移量取反

? ? ? ? ? ? if (vectorForEnd.x <0) {

hDeltaJump = -hDeltaJump;

? ? ? ? ? ? }

}else {

//不能橫向滾動琐旁,橫向位置偏移量當(dāng)然就為0

? ? ? ? ? ? hDeltaJump =0;

? ? ? ? }

//豎向的原理同上

//? ? ? ? if (layoutManager.canScrollVertically()) {

//? ? ? ? ? ? vDeltaJump = estimateNextPositionDiffForFling(layoutManager, getVerticalHelper(layoutManager), 0, velocityY);

//? ? ? ? ? ? if (vectorForEnd.y < 0) {

//? ? ? ? ? ? ? ? vDeltaJump = -vDeltaJump;

//? ? ? ? ? ? }

//? ? ? ? } else {

//? ? ? ? ? ? vDeltaJump = 0;

//? ? ? ? }

? ? ? ? //根據(jù)layoutManager的橫豎向布局方式涮阔,最終橫向位置偏移量和豎向位置偏移量二選一,作為fling的位置偏移量

? ? ? ? int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump;

? ? ? ? if (deltaJump ==0) {

return RecyclerView.NO_POSITION;

? ? ? ? }

//當(dāng)前位置加上偏移位置灰殴,就得到fling結(jié)束時的位置敬特,這個位置就是targetPosition

? ? ? ? int targetPos = currentPosition + deltaJump;

? ? ? ? if (targetPos <0) {

targetPos =0;

? ? ? ? }

if (targetPos >= itemCount) {

targetPos = itemCount -1;

? ? ? ? }

return targetPos;

? ? }

private int distanceToStart(View targetView, OrientationHelper helper) {

//找到targetView的坐標(biāo)

? ? ? ? return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding();

? ? }

@Override

public View findSnapView(RecyclerView.LayoutManager layoutManager) {

return findStartView(layoutManager, getHorizontalHelper(layoutManager));

? ? }

private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {

int childCount = layoutManager.getChildCount();

? ? ? ? if (childCount ==0) {

return null;

? ? ? ? }

View closestChild =null;

? ? ? ? //找到RecyclerView的left坐標(biāo)

? ? ? ? final int startOrLeft;

? ? ? ? if(layoutManager.getClipToPadding()){

startOrLeft = helper.getStartAfterPadding();

? ? ? ? }else {

startOrLeft =0;

? ? ? ? }

int absClosest = Integer.MAX_VALUE;

? ? ? ? //遍歷當(dāng)前l(fā)ayoutManager中所有的ItemView

? ? ? ? for (int i =0; i < childCount; i++) {

final View child = layoutManager.getChildAt(i);

? ? ? ? ? ? //itemView的坐標(biāo)

? ? ? ? ? ? int childLeft = helper.getDecoratedStart(child);

? ? ? ? ? ? //計算此ItemView與RecyclerView左邊坐標(biāo)的距離

? ? ? ? ? ? int absDistance = Math.abs(childLeft - startOrLeft);

? ? ? ? ? ? //對比每個ItemView距離到RecyclerView左邊點的距離,找到那個最靠近左邊的itemView然后返回

? ? ? ? ? ? if (absDistance < absClosest) {

absClosest = absDistance;

? ? ? ? ? ? ? ? closestChild = child;

? ? ? ? ? ? ? ? int s = layoutManager.getPosition(child);

? ? ? ? ? ? ? ? Log.e("通知",s+"-------");

? ? ? ? ? ? }

}

return closestChild;

? ? }

private int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager, OrientationHelper helper, int velocityX, int velocityY) {

//計算滾動的總距離牺陶,這個距離受到觸發(fā)fling時的速度的影響

? ? ? ? int[] distances = calculateScrollDistance(velocityX, velocityY);

? ? ? ? //計算每個ItemView的長度

? ? ? ? float distancePerChild = computeDistancePerChild(layoutManager, helper);

? ? ? ? if (distancePerChild <=0) {

return 0;

? ? ? ? }

//這里其實就是根據(jù)是橫向布局還是縱向布局伟阔,來取對應(yīng)布局方向上的滾動距離

? ? ? ? int distance = Math.abs(distances[0]) > Math.abs(distances[1]) ? distances[0] : distances[1];

? ? ? ? //distance的正負(fù)值符號表示滾動方向,數(shù)值表示滾動距離掰伸。橫向布局方式皱炉,內(nèi)容從右往左滾動為正;豎向布局方式狮鸭,內(nèi)容從下往上滾動為正

? ? ? ? // 滾動距離/item的長度=滾動item的個數(shù)合搅,這里取計算結(jié)果的整數(shù)部分

? ? ? ? if (distance >0) {

return (int) Math.floor(distance / distancePerChild);

? ? ? ? }else {

return (int) Math.ceil(distance / distancePerChild);

? ? ? ? }

}

private float computeDistancePerChild(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {

View minPosView =null;

? ? ? ? View maxPosView =null;

? ? ? ? int minPos = Integer.MAX_VALUE;

? ? ? ? int maxPos = Integer.MIN_VALUE;

? ? ? ? int childCount = layoutManager.getChildCount();

? ? ? ? if (childCount ==0) {

return INVALID_DISTANCE;

? ? ? ? }

//循環(huán)遍歷layoutManager的itemView,得到最小position和最大position怕篷,以及對應(yīng)的view

? ? ? ? for (int i =0; i < childCount; i++) {

View child = layoutManager.getChildAt(i);

? ? ? ? ? ? final int pos = layoutManager.getPosition(child);

? ? ? ? ? ? if (pos == RecyclerView.NO_POSITION) {

continue;

? ? ? ? ? ? }

if (pos < minPos) {

minPos = pos;

? ? ? ? ? ? ? ? minPosView = child;

? ? ? ? ? ? }

if (pos > maxPos) {

maxPos = pos;

? ? ? ? ? ? ? ? maxPosView = child;

? ? ? ? ? ? }

}

if (minPosView ==null || maxPosView ==null) {

return INVALID_DISTANCE;

? ? ? ? }

//最小位置和最大位置肯定就是分布在layoutManager的兩端历筝,但是無法直接確定哪個在起點哪個在終點(因為有正反向布局)

? ? ? ? //所以取兩者中起點坐標(biāo)小的那個作為起點坐標(biāo)

? ? ? ? //終點坐標(biāo)的取值一樣的道理

? ? ? ? int start = Math.min(helper.getDecoratedStart(minPosView), helper.getDecoratedStart(maxPosView));

? ? ? ? int end = Math.max(helper.getDecoratedEnd(minPosView), helper.getDecoratedEnd(maxPosView));

? ? ? ? //終點坐標(biāo)減去起點坐標(biāo)得到這些itemview的總長度

? ? ? ? int distance = end - start;

? ? ? ? if (distance ==0) {

return INVALID_DISTANCE;

? ? ? ? }

// 總長度 / itemview個數(shù) = itemview平均長度

? ? ? ? return 1f * distance / ((maxPos - minPos) +1);

? ? }

private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {

if (mHorizontalHelper ==null) {

mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);

? ? ? ? }

return mHorizontalHelper;

? ? }

}

? 以上是整體代碼。

需要注意的是onScrollStateChanged() 會執(zhí)行兩次廊谓,自己判斷下梳猪。

關(guān)鍵地方在圖1部分

圖1

更改startOrLeft這個值,就可以達到自己想要的效果

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒸痹,一起剝皮案震驚了整個濱河市春弥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叠荠,老刑警劉巖匿沛,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異榛鼎,居然都是意外死亡逃呼,警方通過查閱死者的電腦和手機鳖孤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抡笼,“玉大人苏揣,你說我怎么就攤上這事⊥埔觯” “怎么了平匈?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長藏古。 經(jīng)常有香客問我增炭,道長,這世上最難降的妖魔是什么拧晕? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任隙姿,我火速辦了婚禮,結(jié)果婚禮上防症,老公的妹妹穿的比我還像新娘孟辑。我一直安慰自己,他們只是感情好蔫敲,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布饲嗽。 她就那樣靜靜地躺著,像睡著了一般奈嘿。 火紅的嫁衣襯著肌膚如雪貌虾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天裙犹,我揣著相機與錄音尽狠,去河邊找鬼。 笑死叶圃,一個胖子當(dāng)著我的面吹牛袄膏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掺冠,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沉馆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了德崭?” 一聲冷哼從身側(cè)響起斥黑,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎眉厨,沒想到半個月后锌奴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡憾股,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年鹿蜀,在試婚紗的時候發(fā)現(xiàn)自己被綠了箕慧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡茴恰,死狀恐怖销钝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情琐簇,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布座享,位于F島的核電站婉商,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏渣叛。R本人自食惡果不足惜丈秩,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淳衙。 院中可真熱鬧蘑秽,春花似錦、人聲如沸箫攀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽靴跛。三九已至缀雳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梢睛,已是汗流浹背肥印。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绝葡,地道東北人深碱。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像藏畅,于是被迫代替她去往敵國和親敷硅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容