github地址:https://github.com/wsj1024/RvParallaxImageView 欢迎star

一,前言

在知乎和花生地铁app上看到过类似如下的效果:

​ 在recyclerview中,某一个item位置显示广告图片,广告图是可以填充屏幕的大图,recyclerview滚动时,图片的显示区域可以跟着滚动。这个功能完美解决了:在位置空间不足的情况下展示一张完整广告图的需求。

​ 于是乎就有了RvParallaxImageView.RvParallaxImageView没有任何侵入性,默认提供了加载resource中的drawable、和加载本地磁盘上的图片两种方式。并且提供了灵活的扩展方式,可以利用自己项目中的图片加载库进行加载。

​ 比如你使用GlidePicasso网络加载框架加载网络图片,可以使用GlideImageController/ PicassoImageController进行加载,请参看demo。

​ 当然你也可以自定义Controller来使用其他图片加载框架进行加载。

​ 总结:PicassoImageController具有零侵入,使用简单,扩展方便等特点。

二,使用

​ 为了不对你的代码由任何侵入性,PicassoImageController默认提供了加载res资源中的drawable和加载本地sd卡中图片两种方式。如果你需要进行网络加载,可以非常简单地自行扩展。demo中提供了GlidePicasso两种扩展的方式。

1,首先添加依赖:

Via Gradle:

allprojects {repositories {maven { url 'https://jitpack.io' }}
}dependencies {implementation 'com.github.wsj1024:RvParallaxImageView:1.0.2'
}

2,实现recyclerview效果

​ 假设你对recyclerview比较熟悉,比如有如下代码:

activity:

recyclerView.addItemDecoration(DividerItemDecoration(this,DividerItemDecoration.VERTICAL))
recyclerView.adapter = MyAdapter(recyclerView)

​ 在adapter中根据getItemViewType设置显示不同的布局

adapter:

class MyAdapter(private val recyclerView: RecyclerView) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {override fun getItemViewType(position: Int): Int {return if (position != 0 && position % 5 == 0) 1 else 0}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {// 根据不同type返回不同viewholderreturn if (viewType == 1) {val view = LayoutInflater.from(parent.context).inflate(R.layout.recycler_image, parent, false)ImageViewViewHolder(view)} else {val view =LayoutInflater.from(parent.context).inflate(R.layout.recycler_item, parent, false)MyViewHolder(view)}}override fun getItemCount(): Int {return 25}override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {when (position) {5 -> {// todo 展示RvParallaxImageView}10 -> {// todo 展示RvParallaxImageView}else -> {(holder as MyViewHolder).tvTitle.text = "position:$position"}}}// 普通viewholderclass MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {val tvTitle = view.findViewById<TextView>(R.id.tvTitle)}// RvParallaxImageView的viewholderclass ImageViewViewHolder(view: View) : RecyclerView.ViewHolder(view) {val parallaxImageView = view.findViewById<ScrollWithRvImageView>(R.id.parallaxImageView)val tvTitle = view.findViewById<TextView>(R.id.tvTitle)}
}

3,加载资源中的drawable

RvParallaxImageView默认提供了加载资源中的drawable的方式,通过对parallaxImageView绑定recyclerview并设置ResImageController来实现。代码如下:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {when (position) {5 -> {      // 资源图(holder as ImageViewViewHolder).parallaxImageView.apply {bindRecyclerView(recyclerView)setController(ResImageController(context, R.mipmap.girl))}holder.tvTitle.text = "加载资源图:R.mipmap.girl"}...}
}

4,加载本地sd卡中的图片

​ 同上,RvParallaxImageView默认也提供了加载本地sd卡中的图片的方式,通过对parallaxImageView绑定recyclerview并设置LocalImageController来实现。代码如下:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {when (position) {...10 -> {     // 本地图val imagePath = pathPrefix + "a0.jpg";(holder as ImageViewViewHolder).parallaxImageView.apply {bindRecyclerView(recyclerView)setController(LocalImageController(imagePath))}holder.tvTitle.text = "加载本地图: /sdcard/a0.jpg"}...}
}

注意,此处需要申请读sd卡权限

三,加载网络图片

​ 1,自定义ImageController

​ 如需加载网络图片需要自定义ImageController,这个过程也非常简单:首先新建class继承自BaseImageController,实现loadImage方法,然后在loadImage中利用图片加载库获取bitmap或drawable,然后调用handleBitmaphandleDrawable方法即可。

​ 如demo中的GlideImageController:

public class GlideImageController extends BaseImageController {private Context mContext;private String imageUrl;public GlideImageController(Context context, String imageUrl) {this.mContext = context;this.imageUrl = imageUrl;}@Overrideprotected void loadImage(int viewWidth) {// 利用Glide获取drawableGlide.with(mContext).load(imageUrl).into(new SimpleTarget<Drawable>() {@Overridepublic void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {// 处理drawablehandleDrawable(viewWidth, resource);}});}
}

​ 2,使用

​ 定义好ImageController后使用起来也非常简单如下:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {when (position) {15 -> {     // Glide加载val imageUrl = "http://gitstar.com.cn:8000/static/img/1.jpg"(holder as ImageViewViewHolder).parallaxImageView.apply {bindRecyclerView(recyclerView)setController(GlideImageController(context, imageUrl))}}20 -> {     // picasso加载val imageUrl = "http://gitstar.com.cn:8000/static/img/6.jpg"(holder as ImageViewViewHolder).parallaxImageView.apply {bindRecyclerView(recyclerView)setController(PicassoImageController(context, imageUrl))}}}
}

四,实现原理

1,核心思想

​ 这种视差效果的核心思想是怎样的呢?

​ 如下图,假设黑色区域是recyclerview,红色区域是item,黄色区域是图片。此时item的底部正好在recyclerview的底部,item展示的也是图片的底部。根据我们的逻辑在item滚动到recyclerview顶部时图片也刚好滚动到item的顶部,因此就存在了一个滚动中的缩放因子。

​ 那么这个缩放因子怎么计算呢?

​ 上图中h1是图片未在item中展示的高度,h2是item距离recyclerview顶部的距离,要想让item走完h2的距离时,image走完h1的距离,那么这个缩放因子就是scaleFactor = h1/h2。recyclerview在滚动中移动的距离dy*scaleFactor也就是图片移动的距离。

2,ImageController

​ ImageController是用来加载图片,对图片进行缩放处理,并提供处理后bitmap给RvParallaxImageView的控制器。其结果如下图所示,主要有接口IController,抽象类BaseImageController和几种加载图片方式的实现类,如需扩展请继承BaseImageController

BaseImageController中提供了对Drawable和Bitmap进行缩放处理方法,因此其子类中只需要获取到Drawable或Bitmap交给父类来处理即可。

3,RvParallaxImageView

RvParallaxImageView是一个自定义view,其绘制的图片来源于ImageController,图片的视差移动效果由recyclerview滚动来决定。

​ 3.1 setController

​ 使用RvParallaxImageView时需要对其设置Controller,然后需要Controller添加ProcessCallback就是处理完成后的回调,回调结果返回了图片缩放后的宽高。

​ 根据图片的高度以及当前view的高度可以计算出上文的h1,根据当前view的高度以及recyclerview的高度可以计算出上文的h2,因此可以在此时计算出scaleFactor的值。

public void setController(IController controller) {mImageController = controller;mImageController.setProcessCallback(new ProcessCallback() {@Overridepublic void onProcessFinished(int width, int height) {isScaled = true;resetScaleFactor(height);getLocationInWindow(viewLocation);topOffscreen = -(viewLocation[1] - rvLocation[1]) * scaleFactor;bindTopOrBottom();// 当前是非ui线程postInvalidate();}});
}

​ 3.2,bindRecyclerView

​ 从使用中可以发现RvParallaxImageView绑定了RecyclerView,并监听RecyclerView的滚动,根据滚动的位置以及缩放因子scaleFactor计算出topOffscreen来确定图片顶部展示的位置,图片顶部展示的位置也就决定了图片在视图中显示的区域。

public void bindRecyclerView(RecyclerView recyclerView) {if (recyclerView == null || recyclerView.equals(this.recyclerView)) {return;}unbindRecyclerView();this.recyclerView = recyclerView;rvLocation = new int[2];rvHeight = recyclerView.getLayoutManager().getHeight();recyclerView.getLocationInWindow(rvLocation);recyclerView.addOnScrollListener(rvScrollListener = new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);}@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);int topDistance = getTopDistance();if (topDistance > 0 && topDistance + viewHeight < rvHeight) {topOffscreen += dy * scaleFactor;bindTopOrBottom();if (isMeasured) {invalidate();}} else if (topDistance + viewHeight >= rvHeight) {// view还未显示出来就执行process回调,因此会出现view在底部图片置顶的情况if (topOffscreen == 0) {if (isScaled) {getLocationOnScreen(viewLocation);topOffscreen = -(viewLocation[1] - rvLocation[1]) * scaleFactor;bindTopOrBottom();invalidate();}}}}});
}

​ 3.3 onDraw()

​ 最后在onDraw()中根据topOffscreen绘制bitmap,实现图片展示:

protected void onDraw(Canvas canvas) {super.onDraw(canvas);Bitmap bitmap = mImageController.getTargetBitmap();if (bitmap == null || bitmap.isRecycled()) {return;}canvas.drawBitmap(bitmap, 0, topOffscreen, null);
}

topOffscreen可以理解为滚动中实时变动的h1的值

​ github地址:https://github.com/wsj1024/RvParallaxImageView 欢迎star

​ 本人三线小博主,欢迎大佬们指导批评。

​ 最后给出res/sdcard/glide/picasso四种方式的效果图:

仿知乎列表广告栏:在RecyclerView中实现大图片完整展示的视差效果(优雅地插入全屏广告图)相关推荐

  1. Taro框架中 Image 和 Video 组件预览图片/视频时添加明显的关闭按钮以关闭全屏预览

    需求 Taro框架中 Image 和 Video 组件预览图片/视频时 添加明显的关闭按钮 以关闭全屏观看,避免用户直接操作返回后导致页面空白(原有消息记录消失 - 重新进入项目首页) [补充] 全屏 ...

  2. 使用html仿支付宝首页,jQuery实现仿Alipay支付宝首页全屏焦点图切换特效

    本文实例讲述了jQuery实现仿Alipay支付宝首页全屏焦点图切换特效.分享给大家供大家参考.具体实现方法如下: /p> "http://www.w3.org/TR/xhtml1/D ...

  3. HTML5期末大作业:在线音乐播放器网站设计——html5全屏的音乐列表播放器页面源码 HTML+CSS+JavaScript 学生DW网页设计作业成品 web课程设计网页规划与设计 计算机毕设

    HTML5期末大作业:在线音乐播放器网站设计--html5全屏的音乐列表播放器页面源码 HTML+CSS+JavaScript 学生DW网页设计作业成品 web课程设计网页规划与设计 计算机毕设网页设 ...

  4. 学习在网页中应用大图片背景的20个精美案例

    定义网站基调和风格的一种简单方式就是使用大图片背景(Big Background Image),在使用这种大图片的时候,为了让大背景图片能够和网站的其它内容很好的融合在一起,你需要考虑很多事情,否则可 ...

  5. Android中使用自定义的VideoController和MediaPlayer实现视频的窗口和全屏播放

    基于MediaPlayer的能窗口和全屏切换的视屏播放器 之前在一个项目中做了一个能窗口化和全屏切换的播放器,做之前在网上也看了很多的demo,今天为了记录下自己的学习成果,特意将它写下来供自己以后参 ...

  6. 包括edge,Chrome,火狐、百度,360等浏览器怎么全屏_如何在Microsoft Edge中启用和禁用全屏模式以及解决浏览器无法开启或关闭全屏的问题

    文章目录 1. 引出问题 2. 解决问题 2.1 使用缩放菜单 2.2 Win + Shift + Enter窍门 2.3 最大化与全屏模式 2.4 以全屏模式观看网络视频 3. 重要总结 4. 解决 ...

  7. 经典效果组件篇1——工程中插入悠米全屏广告

    最近用到在软件主体中插入有米广告插件,但是在网上不论哪个平台搜的都是一份,就是以"经过了一番折腾,忙忙碌碌了一下午,终于......"开头的那篇,写的不是很详细,而且我自己应用的时 ...

  8. 【小程序】仿知乎——知识付费类小程序(大咖咨询、Live推荐...)

    1软件功能说明 "听我说"是一个知识付费分享平台,服务包括优质音频信息提供.个性化咨询.知识和技能分享等.将邀请行业大咖到平台进行分享,以此获得影响力和知识变现收益.同时为多个领域 ...

  9. 仿京东淘宝商品详情页中视频和图片的轮播功能

    还没有学会如何上传视频到博客上,先上传图片吧 案例下载地址: https://download.csdn.net/download/dawnzeng/10430298 视频播放借用了饺子播放器,最主要 ...

最新文章

  1. CCNP交换实验(3) -- STP
  2. mysql 查询表结构 几种方法
  3. MFC的类层次结构图
  4. windows 7 旗舰版下无法安装 msi 文件 解决办法
  5. Java程序员转Android开发必读经验
  6. Spring MVC如何接收浏览器传递来的请求参数--request--形参--实体类封装
  7. 我们用过的linux系统
  8. oracle更改语句用 怎么站位,Oracle 数据库如何修改控制文件的位置
  9. php 编译 iconv错误,php编译错误:configure: error: Please reinstall the iconv library.
  10. 图解算法之排序算法(5)——归并排序
  11. 如何在SQL Server VARCHAR / NVARCHAR字符串中插入换行符
  12. Ansbile实战经验
  13. 同样的事情,小孩叫逆反,大人叫抬杠
  14. txt转excel的工具
  15. 细述 wxWindows
  16. 基于SSM的个人健康管理系统
  17. jsonrpc php使用,基于php的json rpc原理及应用
  18. Vscode关闭自动更新
  19. 15.内置函数,匿名函数
  20. 《禅与摩托车维修艺术》读后感

热门文章

  1. 知乎50万人收藏的资源网站:什么资源都能秒速找到
  2. javascript_抛物线系列_04已知起点和终点画抛物线
  3. 程序员被迫辞职的5种情况!
  4. 大专拿不到计算机考证能毕业吗
  5. Book List: 2006 Book list
  6. 中国旅游研究院携程:2018中国在线旅游发展大数据指数报告(附下载)
  7. linux更新应用的命令,linux常用的软件更新命令,自己操作不求人!
  8. paypal ipn java_PayPal IPN验证
  9. 北京尚学堂偷偷告诉你:作为程序员必备的基本品质
  10. XNA 3.0小例子