}
}

if (dx > 0) {
if (mHorizontalOffset >= getMaxOffset()) {
// 根据最大偏移量来计算滑动到最右侧边缘
mHorizontalOffset = (long) getMaxOffset();
dx = 0;
}
}

// 分离全部的view,加入到临时缓存
detachAndScrapAttachedViews(recycler);

float startX = 0;
float fraction = 0f;
boolean isChildLayoutLeft = true;

View tempView = null;
int tempPosition = -1;

if (onceCompleteScrollLength == -1) {
// 因为mFirstVisiPos在下面可能被改变,所以用tempPosition暂存一下
tempPosition = mFirstVisiPos;
tempView = recycler.getViewForPosition(tempPosition);
measureChildWithMargins(tempView, 0, 0);
childWidth = getDecoratedMeasurementHorizontal(tempView);
}

// 修正第一个可见view mFirstVisiPos 已经滑动了多少个完整的onceCompleteScrollLength就代表滑动了多少个item
firstChildCompleteScrollLength = getWidth() / 2 + childWidth / 2;
if (mHorizontalOffset >= firstChildCompleteScrollLength) {
startX = normalViewGap;
onceCompleteScrollLength = childWidth + normalViewGap;
mFirstVisiPos = (int) Math.floor(Math.abs(mHorizontalOffset - firstChildCompleteScrollLength) / onceCompleteScrollLength) + 1;
fraction = (Math.abs(mHorizontalOffset - firstChildCompleteScrollLength) % onceCompleteScrollLength) / (onceCompleteScrollLength * 1.0f);
} else {
mFirstVisiPos = 0;
startX = getMinOffset();
onceCompleteScrollLength = firstChildCompleteScrollLength;
fraction = (Math.abs(mHorizontalOffset) % onceCompleteScrollLength) / (onceCompleteScrollLength * 1.0f);
}

// 临时将mLastVisiPos赋值为getItemCount() - 1,放心,下面遍历时会判断view是否已溢出屏幕,并及时修正该值并结束布局
mLastVisiPos = getItemCount() - 1;

float normalViewOffset = onceCompleteScrollLength * fraction;
boolean isNormalViewOffsetSetted = false;

//----------------3、开始布局-----------------
for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
View item;
if (i == tempPosition && tempView != null) {
// 如果初始化数据时已经取了一个临时view
item = tempView;
} else {
item = recycler.getViewForPosition(i);
}

addView(item);
measureChildWithMargins(item, 0, 0);

if (!isNormalViewOffsetSetted) {
startX -= normalViewOffset;
isNormalViewOffsetSetted = true;
}

int l, t, r, b;
l = (int) startX;
t = getPaddingTop();
r = l + getDecoratedMeasurementHorizontal(item);
b = t + getDecoratedMeasurementVertical(item);

layoutDecoratedWithMargins(item, l, t, r, b);

startX += (childWidth + normalViewGap);

if (startX > getWidth() - getPaddingRight()) {
mLastVisiPos = i;
break;
}
}
return dx;
}

涉及的方法:

/**

  • 最大偏移量
  • @return
    */
    private float getMaxOffset() {
    if (childWidth == 0 || getItemCount() == 0) return 0;
    return (childWidth + normalViewGap) * (getItemCount() - 1);
    }

/**

  • 获取某个childView在水平方向所占的空间,将margin考虑进去
  • @param view
  • @return
    */
    public int getDecoratedMeasurementHorizontal(View view) {
    final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
    view.getLayoutParams();
    return getDecoratedMeasuredWidth(view) + params.leftMargin
  • params.rightMargin;
    }

/**

  • 获取某个childView在竖直方向所占的空间,将margin考虑进去
  • @param view
  • @return
    */
    public int getDecoratedMeasurementVertical(View view) {
    final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
    view.getLayoutParams();
    return getDecoratedMeasuredHeight(view) + params.topMargin
  • params.bottomMargin;
    }

回收复用

这里使用Android仿豆瓣书影音频道推荐表单堆叠列表RecyclerView-LayoutManager中使用的回收技巧:

/**

  • @param recycler
  • @param state
  • @param delta
    */
    private int fill(RecyclerView.Recycler recycler, RecyclerView.State state, int delta) {
    int resultDelta = delta;
    //。。。省略

recycleChildren(recycler);
log(“childCount= [” + getChildCount() + “]” + “,[recycler.getScrapList().size():” + recycler.getScrapList().size());
return resultDelta;
}

/**

  • 回收需回收的Item。
    */
    private void recycleChildren(RecyclerView.Recycler recycler) {
    List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
    for (int i = 0; i < scrapList.size(); i++) {
    RecyclerView.Vi
    ewHolder holder = scrapList.get(i);
    removeAndRecycleView(holder.itemView, recycler);
    }
    }

回收复用这里就不验证了,感兴趣的小伙伴可自行验证。

动画效果

private int fillHorizontalLeft(RecyclerView.Recycler recycler, RecyclerView.State state, int dx) {
// 省略 …
//----------------3、开始布局-----------------
for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
// 省略 …

// 缩放子view
final float minScale = 0.6f;
float currentScale = 0f;
final int childCenterX = (r + l) / 2;
final int parentCenterX = getWidth() / 2;
isChildLayoutLeft = childCenterX <= parentCenterX;
if (isChildLayoutLeft) {
final float fractionScale = (parentCenterX - childCenterX) / (parentCenterX * 1.0f);
currentScale = 1.0f - (1.0f - minScale) * fractionScale;
} else {
final float fractionScale = (childCenterX - parentCenterX) / (parentCenterX * 1.0f);
currentScale = 1.0f - (1.0f - minScale) * fractionScale;
}
item.setScaleX(currentScale);
item.setScaleY(currentScale);
item.setAlpha(currentScale);

layoutDecoratedWithMargins(item, l, t, r, b);
// 省略 …
}
return dx;
}

childView 越向屏幕中间移动缩放比越大,越向两边移动缩放比越小。

自动选中

1、滚动停止后自动选中

监听 onScrollStateChanged,在滚动停止时计算出应当停留的 position,再计算出停留时的 mHorizontalOffset 值,播放属性动画将当前 mHorizontalOffset 不断更新至最终值即可。相关代码如下:

@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
switch (state) {
case RecyclerView.SCROLL_STATE_DRAGGING:
//当手指按下时,停止当前正在播放的动画
cancelAnimator();
break;
case RecyclerView.SCROLL_STATE_IDLE:
//当列表滚动停止后,判断一下自动选中是否打开
if (isAutoSelect) {
//找到离目标落点最近的item索引
smoothScrollToPosition(findShouldSelectPosition());
}
break;
default:
break;
}
}

/**

  • 平滑滚动到某个位置
  • @param position 目标Item索引
    */
    public void smoothScrollToPosition(int position) {
    if (position > -1 && position < getItemCount()) {
    startValueAnimator(position);
    }
    }

private int findShouldSelectPosition() {
if (onceCompleteScrollLength == -1 || mFirstVisiPos == -1) {
return -1;
}
int position = (int) (Math.abs(mHorizontalOffset) / (childWidth + normalViewGap));
int remainder = (int) (Math.abs(mHorizontalOffset) % (childWidth + normalViewGap));
// 超过一半,应当选中下一项
if (remainder >= (childWidth + normalViewGap) / 2.0f) {
if (position + 1 <= getItemCount() - 1) {
return position + 1;
}
}
return position;
}

private void startValueAnimator(int position) {
cancelAnimator();

final float distance = getScrollToPositionOffset(position);

long minDuration = 100;
long maxDuration = 300;
long duration;

float distanceFraction = (Math.abs(distance) / (childWidth + normalViewGap));

if (distance <= (childWidth + normalViewGap)) {
duration = (long) (minDuration + (maxDuration - minDuration) * distanceFraction);
} else {
duration = (long) (maxDuration * distanceFraction);
}
selectAnimator = ValueAnimator.ofFloat(0.0f, distance);
selectAnimator.setDuration(duration);
selectAnimator.setInterpolator(new LinearInterpolator());
final float startedOffset = mHorizontalOffset;
selectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
mHorizontalOffset = (long) (startedOffset + value);
requestLayout();
}
});
selectAnimator.start();
}

2、点击非焦点view自动将其选中为焦点view

我们可以直接拿到 viewposition,直接调用 smoothScrollToPosition 方法,就可以实现自动选中为焦点。

中间view覆盖在两边view之上

效果是这样的:

从效果中可以看出,索引为2的view覆盖在1,3的上面,同时1又覆盖在0的上面,以此内推。

RecyclerView 继承于 ViewGroup ,那么在添加子view addView(View child, int index)index 的索引值越大,越显示在上层。那么可以得出,为2的绿色卡片被添加是 index 最大,分析可以得出以下结论:

index 的大小:

0 < 1 < 2 > 3 > 4

中间最大,两边逐渐减小的原则。

获取到中间 view 的索引值,如果小于等于该索引值则调用 addView(item) ,反之调用 addView(item, 0) ;相关代码如下:

private int fillHorizontalLeft(RecyclerView.Recycler recycler, RecyclerView.State state, int dx) {
//省略 …
//----------------3、开始布局-----------------
for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
//省略 …
int focusPosition = (int) (Math.abs(mHorizontalOffset) / (childWidth + normalViewGap));
if (i <= focusPosition) {
addView(item);
} else {
recycler, RecyclerView.State state, int dx) {
//省略 …
//----------------3、开始布局-----------------
for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
//省略 …
int focusPosition = (int) (Math.abs(mHorizontalOffset) / (childWidth + normalViewGap));
if (i <= focusPosition) {
addView(item);
} else {

Android强行进阶,自定义控件—LayoutManager,android开发视频相关推荐

  1. 视频教程-HTML5进阶提升与案例开发视频课程-HTML5/CSS

    HTML5进阶提升与案例开发视频课程 中国实战派HTML5培训第一人,微软技术讲师,曾任百合网技术总监,博看文思HTML5总监.陶国荣长期致力于HTML5.JavaScript.CSS3.jQuery ...

  2. Android工程师进阶之路 Android开发进阶 从小工到专家 上市啦

    封面 目录1 目录2 - 当当购买链接 - 京东购买链接 为什么写这本书 写这本书的念头由来已久了.也许是从我打算写<Android源码设计模式解析与实战>那时起就萌生了这个念头,因为设计 ...

  3. Android强行进阶:为何大厂APP如微信,焦虑的移动互联网开发者如何破局

    ABI是英文Application Binary Interface的缩写,及应用二进制接口. 不同Android设备,使用的CPU架构可能不同,因此支持不同的指令集. CPU 与指令集的每种组合都有 ...

  4. android服务进阶,我的Android进阶之旅------Android服务的生命周期回调方法

    先引用一段官网上的文字 ======================================================================================== ...

  5. Android高手进阶教程(一)-------Android常用名令集锦(图文并茂)!

    大家好,今天我们要讲的是android开发中,比较常用的名令集锦, 在我们开发中难免用到Android命令,有些确实命令确实很有用处. 特别对于一些初学者来说,命令根本没有想过用也不会用,比如他们想安 ...

  6. android如何让自定义控件居中,Android自定义控件之自定义TextView,实现drawableLeft可以和文字一起居中...

    如何实现使用TextView的DrawableLeft使图片和文字居中显示呢??? 代码如下: 1.首先自定义一个类,继承TextViewpackage com.test.signcalendar.w ...

  7. Android强行进阶:为何大厂APP如微信、支付宝、淘宝、手Q等只适配了armeabi-v7a/armeabi?

    0. 前言 前几天啊,在公众号发了一篇文章<优化ApK大小之ABI Filters 和 APK split>,评论区收到了一些留言说,文章讲得不够深入,关于系统是如何选择不同abi下的so ...

  8. Android强行进阶—按键事件焦点事件攻略

    前言 对于Android手机APP普通开发者来说,KeyEvent接触相对较少,相反接触较多的应该是TouchEvent.而Android TV开发者对KeyEvent的接触就非常频繁.这也是手机应用 ...

  9. Android工程师进阶第九课 Android优化实战

    第24讲:APK 如何做到包体积优化? 关于 APK Size 的优化,网上有很多版本的介绍.但是因为每个项目的背景.实现方式都不尽相同,导致各个项目之间能列出的共性相对较少.所以这节课我主要分享一下 ...

最新文章

  1. 在iOS中使用tableView
  2. 微信公众号中ip白名单用谁的ip
  3. python 发邮件-带附件-文本-html
  4. oracle 双结点监听文件,RAC监听服务两个节点 只能起一个
  5. root无法运行命令解决办法
  6. 智慧屏用鸿蒙的生态,紧随鸿蒙OS手机版 ,智慧屏为什么对鸿蒙生态这么重要?...
  7. qtp12版本下载安装破解教程
  8. Linux 目录文件处理文件
  9. linux 自动安装脚步,linux自动安装lnmp脚步
  10. php把amr转换成mp3,php 微信amr转mp3的方法
  11. Ubuntu录制gif图
  12. 美国佐治亚理工学院计算机博士,大神offer | 恭喜G同学全奖录取佐治亚理工学院-数学博士!...
  13. 关于 NM_CONTROLLED和Network Manager
  14. DITHER 抖动算法
  15. linux gpio口测试程序,gpio接口测试
  16. Y430P拆机:安装固态硬盘+内存+重装系统梳理
  17. 【图解】AC97、HD音效卡前置音频线的接法
  18. 图像传感器binning_图像传感器的两种缩放模式
  19. 【MySQL】# mysql计算两个时间的差值
  20. C语言求离散数学中析取合取等

热门文章

  1. 动态域名解析服务器离线会引起什么_动态域名解析过程中可能出现的问题及解决方案...
  2. nestjs[typeorm学习之一对一表关系探究与使用]
  3. 电子天平的检定和检定结果的影响因素
  4. c# 调用zebra打印指令 打印到USB端口
  5. 企业邮件网关首选:TurboGate反垃圾邮件网关
  6. 实战:大数据营销 微信朋友圈广告
  7. 读书笔记《Outlier Analysis》 第八章 分类、文本和混合属性中的异常检测
  8. python与分形0021 - 【教程】奥林匹克五环
  9. Matplotlib之扇形图绘制
  10. amd r7 2700u linux,满血双通道R7 2700U?AMD锐龙APU测试