海报分享功能实现详解

});
setDefaultBackground();
}

private void notifyBackgroundChange() {
if (mPosterModule == null || mPosterModule.getBannerInfo().size() == 0) {
setDefaultBackground();
return;
}

/**

  • 延时设置说明,由于滑动距离会出现正好一页的距离或偏离.
  • 所以滑动停止事件触发会出现一次或两次(偏离的时候,偏差.
  • 量将自动修正后再次停止),所以延时并取消上一次背景切换可以消除画面闪烁。.
    */
    mBlurView.removeCallbacks(mBlurRunnable);
    mBlurRunnable = new Runnable() {
    @Override
    public void run() {
    Bitmap bitmap = mCardScaleHelper.getCurrentBitmap();
    ViewSwitchUtils.startSwitchBackgroundAnim(mBlurView, BlurBitmapUtils.getBlurBitmap(mBlurView.getContext(), bitmap, 15));
    }
    };
    mBlurView.postDelayed(mBlurRunnable, 500);
    }

private void setDefaultBackground() {
if (mBlurView == null) {
return;
}
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_card_default);
mBlurView.setImageBitmap(BlurBitmapUtils.getBlurBitmap(mBlurView.getContext(), bitmap, 15));
}

---- CardScaleHelper.java ----
public Bitmap getCurrentBitmap() {
View view = mRecyclerView.getLayoutManager().findViewByPosition(getCurrentItemPos());
if (view == null) {
return null;
}
ImageView mBgIv = (ImageView) view.findViewById(R.id.iv_bg);
final Bitmap bitmap = ((BitmapDrawable) mBgIv.getDrawable()).getBitmap();
return bitmap;
}

---- ViewSwitchUtils.java ----
public static void startSwitchBackgroundAnim(ImageView view, Bitmap bitmap) {
if (view == null || bitmap == null) {
return;
}
Drawable oldDrawable = view.getDrawable();
Drawable oldBitmapDrawable;
TransitionDrawable oldTransitionDrawable = null;
if (oldDrawable instanceof TransitionDrawable) {
oldTransitionDrawable = (TransitionDrawable) oldDrawable;
oldBitmapDrawable = oldTransitionDrawable.findDrawableByLayerId(oldTransitionDrawable.getId(1));
} else if (oldDrawable instanceof BitmapDrawable) {
oldBitmapDrawable = oldDrawable;
} else {
oldBitmapDrawable = new ColorDrawable(0xffc2c2c2);
}

if (oldTransitionDrawable == null) {
oldTransitionDrawable = new TransitionDrawable(new Drawable[]{oldBitmapDrawable, new BitmapDrawable(view.getResources(), bitmap)});
oldTransitionDrawable.setId(0, 0);
oldTransitionDrawable.setId(1, 1);
oldTransitionDrawable.setCrossFadeEnabled(true);
view.setImageDrawable(oldTransitionDrawable);
} else {
oldTransitionDrawable.setDrawableByLayerId(oldTransitionDrawable.getId(0), oldBitmapDrawable);
oldTransitionDrawable.setDrawableByLayerId(oldTransitionDrawable.getId(1), new BitmapDrawable(view.getResources(), bitmap));
}
oldTransitionDrawable.startTransition(1000);
}

---- BlurBitmapUtils.java ----
/**

  • 得到模糊后的bitmap
  • @param context
  • @param bitmap
  • @param radius
  • @return
    */
    public static Bitmap getBlurBitmap(Context context, Bitmap bitmap, int radius) {
    if (bitmap == null || context == null) {
    return null;
    }
    // 将缩小后的图片做为预渲染的图片。
    Bitmap inputBitmap = Bitmap.createScaledBitmap(bitmap, SCALED_WIDTH, SCALED_HEIGHT, false);
    // 创建一张渲染后的输出图片。
    Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
    try {
    // 创建RenderScript内核对象
    RenderScript rs = RenderScript.create(context);
    // 创建一个模糊效果的RenderScript的工具对象
    ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));

// 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间。
// 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去。
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);

// 设置渲染的模糊程度, 25f是最大模糊度
blurScript.setRadius(radius);
// 设置blurScript对象的输入内存
blurScript.setInput(tmpIn);
// 将输出数据保存到输出内存中
blurScript.forEach(tmpOut);

// 将数据填充到Allocation中
tmpOut.copyTo(outputBitmap);

} catch (Exception e) {
e.printStackTrace();
}finally {
inputBitmap.recycle();
}

return outputBitmap;
}

切换卡片,卡片的缩放效果

我们要实现如上效果,基本的滑动展示,RecyclerView都有实现,需要解决是滑动过程中卡片的缩放问题、卡片透明度变化、滑动距离的判定、页码的计算、多张卡片的内存问题等。

为了复用,主要的代码都是通过帮助类实现。用法如下

---- QRCodePosterActivity.java ----
// mRecyclerView绑定scale效果.
mCardScaleHelper = new CardScaleHelper();
mCardScaleHelper.setCurrentItemPos(0);//初始化指定页面.
mCardScaleHelper.setScale(0.8f);//两侧缩放比例.
mCardScaleHelper.setCardPercentWidth(0.72f);//卡片占屏幕宽度比例.
mCardScaleHelper.attachToRecyclerView(mContentRv);

下面我们来看看具体实现

初始化

我们从绑定开始初始化

---- CardScaleHelper.java ----
private int mCardWidth; // 卡片宽度.
private int mOnePageWidth; // 滑动一页的距离.
private int mCardGalleryWidth;
private int mCurrentItemPos;
private int mCurrentItemOffset;
private float mScale = 0.9f; // 两边视图scale.
private float mCardPercentWidth = 0.60f;//卡片占据屏幕宽度的百分比,需要与CardAdapterHelper中的一致.
private CardPagerSnapHelper mPageSnapHelp = new CardPagerSnapHelper();

public void attachToRecyclerView(final RecyclerView mRecyclerView) {
this.mRecyclerView = mRecyclerView;
mContext = mRecyclerView.getContext();
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
mPageSnapHelp.mNoNeedToScroll = mCurrentItemOffset == 0 || mCurrentItemOffset == getDestItemOffset(mRecyclerView.getAdapter().getItemCount() - 1);
} else {
mPageSnapHelp.mNoNeedToScroll = false;
}
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dx == 0) {
initWidth();
return;
}
// dx>0则表示右滑, dx<0表示左滑, dy<0表示上滑, dy>0表示下滑
mCurrentItemOffset += dx;
computeCurrentItemPos();
onScrolledChangedCallback();
}

});
mPageSnapHelp.attachToRecyclerView(mRecyclerView);
}

/** 初始化卡片宽度**/
private void initWidth() {
mCardGalleryWidth = mRecyclerView.getWidth();
mCardWidth = (int) (mCardGalleryWidth * mCardPercentWidth);
mOnePageWidth = mCardWidth;
mRecyclerView.smoothScrollToPosition(mCurrentItemPos);
onScrolledChangedCallback();
}

计算当前卡片索引

---- CardScaleHelper.java ----
private void computeCurrentItemPos() {
if (mOnePageWidth <= 0) return;
boolean pageChanged = false;
// 滑动超过一页说明已翻页.
if (Math.abs(mCurrentItemOffset - mCurrentItemPos * mOnePageWidth) >= (mOnePageWidth)) {
pageChanged = true;
}
if (pageChanged) {
int tempPos = mCurrentItemPos;
mCurrentItemPos = mCurrentItemOffset / (mOnePageWidth);
}
}

卡片滑动切换计算

下面的这个方法是比较核心,包含了所有卡片的缩放比计算,透明度计算,为了达到平滑过度,这里用到了三角函数,也包含了一些适配问题的解决。由于水平有限,如下方法可能还是存在优化的空间或细节修正,仅供参考,感兴趣的朋友可以自行研究。

---- CardScaleHelper.java ----
/**

  • RecyclerView位移事件监听, view大小随位移事件变化.
    */
    public void onScrolledChangedCallback() {
    for (int i = 0; i < mRecyclerView.getAdapter().getItemCount(); i++) {
    LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
    final View view = layoutManager.getChildAt(i);
    if (view == null) {
    continue;
    }
    //计算当前这个view相对于中间View的偏移页码量.
    //(view相对的X的起始位置-当前scrollview滚动的位置)/每页大小.
    // = 0 为居中页.
    // = 1 为下一页 2 为下下页.
    // = -1 为上一页 -2 为上上页.
    double offsetPage = ((int) view.getTag() * (double) mOnePageWidth - mCurrentItemOffset) / (double) mOnePageWidth;
    double scale = (float) Math.cos(offsetPage);
    if (Math.abs(scale) < mScale)
    scale = mScale;
    view.setScaleX((float) scale);
    view.setScaleY((float) scale);

BigDecimal bd = new BigDecimal((scale * 0.8)).setScale(1, RoundingMode.UP);
if (scale > 0.99f) {
view.setAlpha(1);
} else {
view.setAlpha((bd.floatValue()));
//解决透明显示异常的问题,强制重新绘制.
view.invalidate();
}
}

}

Tag值,及滑动时卡片间隙计算。

---- CardAdapter.java ----
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.itemView.setTag(position);
mCardAdapterHelper.onBindViewHolder(holder.itemView, position, getItemCount());
setQRCodeImageView(holder.mQRCodeIv, holder.mBottomLl);
//业务代码.
}

---- CardScaleHelper.java ----
private int mPagePadding = 15;

public void onBindViewHolder(View itemView, final int position, int itemCount) {
int mOneSideWidth = (int) ((DisplayUtil.getScreenWidth(itemView.getContext()) - itemView.getLayoutParams().width) / 2.0);
int leftMarin = position == 0 ? mOneSideWidth : 0;
int rightMarin = position == itemCount - 1 ? mOneSideWidth : 0;
setViewMargin(itemView, leftMarin, 0, rightMarin, 10);
}

private void setViewMargin(View view, int left, int top, int right, int bottom) {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (lp.leftMargin != left || lp.topMargin != top || lp.rightMargin != right || lp.bottomMargin != bottom) {
lp.setMargins(left, top, right, bottom);
view.setLayoutParams(lp);
}
}

多张卡片内存控制

  • 方案一:利用第三方框架去显示,如glide,pessones等,最简单,如果对内存没有极细的要求的话推荐使用这个方案。
  • 方案二:可以考虑回收不显示卡片的那部分内存,然后利用LruCache进行缓存管理。
指示器

由于指示器比较简单,这里简述一种实现思路, 可以直接用LinearLayout动态添加包含指示器图案的view,每次滑动结束后更新指示器位置。

卡片分享

在铜板街的应用上,卡片最终是要分享出去,所以我们继续分析下,如何在分享前做好准备,由于分享有需要文件,也有需要Bitmap的.

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

image

image

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Of2ZW-1714661007288)]

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值