github地址:https://github.com/giswangsj/RvParallaxImageView 歡迎star
一,前言
在xx地鐵app(可在地鐵上提供wifi)上看到過類似如下的效果:

? 在recyclerview中,某一個item位置顯示廣告圖片,廣告圖是可以填充螢屏的大圖,recyclerview滾動時,圖片的顯示區域可以跟著滾動,這個功能完美解決了:在位置空間不足的情況下展示一張完整廣告圖的需求,
? 于是乎就有了RvParallaxImageView.RvParallaxImageView沒有任何侵入性,默認提供了加載resource中的drawable、和加載本地磁盤上的圖片兩種方式,并且提供了靈活的擴展方式,可以利用自己專案中的圖片加載庫進行加載,
? 比如你使用Glide或Picasso網路加載框架加載網路圖片,可以使用GlideImageController/ PicassoImageController進行加載,請參看demo,
? 當然你也可以自定義Controller來使用其他圖片加載框架進行加載,
? 總結:PicassoImageController具有使用簡單,擴展方便等特點,
二,使用
? 為了不對你的代碼由任何侵入性,PicassoImageController默認提供了加載res資源中的drawable和加載本地sd卡中圖片兩種方式,如果你需要進行網路加載,可以非常簡單地自行擴展,demo中提供了Glide和Picasso兩種擴展的方式,
1,首先添加依賴:
Via Gradle:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.giswangsj:RvParallaxImageView:1.0.1'
}
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回傳不同viewholder
return 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"
}
}
}
// 普通viewholder
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvTitle = view.findViewById<TextView>(R.id.tvTitle)
}
// RvParallaxImageView的viewholder
class 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,然后呼叫handleBitmap或handleDrawable方法即可,
? 如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;
}
@Override
protected void loadImage(int viewWidth) {
// 利用Glide獲取drawable
Glide.with(mContext).load(imageUrl).into(new SimpleTarget<Drawable>() {
@Override
public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
// 處理drawable
handleDrawable(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() {
@Override
public 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() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public 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/giswangsj/RvParallaxImageView 歡迎star
? 最后給出res/sdcard/glide/picasso四種方式的效果圖:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/73550.html
標籤:其他
上一篇:Dr.VAE
