java广告知乎_仿知乎广告效果

这篇文章介绍一个仿知乎广告效果。

先上效果图

1ba270dcdeda

GIF.gif

具体表现在一个recycleView中的某一个item插入一张自己的图。

从图中的效果来看,大致需要几点需求

图片应该是一次性加载进来的,而且图片的宽高是根据recycleView等比缩放的

图片需要跟随recycleView的滚动而滚动

这时刚好想到一种图片的滚动方式canvas.translate()这个api起到的作用是移动画布图层.

具体实现

将一张图按宽等比缩放,铺满整个画布。然后根据recycleView的移动来调用canvas.translate()来不断重绘图片。

下面贴上代码

AdvertisementImageActivity.class

一个recycleView,两个item类型

RecyclerView rv_content;

private static final int LIST_TYPE_AD = 0x11;

private static final int LIST_TYPE_NORMAL = LIST_TYPE_AD + 1;

适配器MyAdapter

class MyAdapter extends RecyclerView.Adapter {

@Override

public int getItemViewType(int position) {

if (position == 10) {

return LIST_TYPE_AD;

}

return LIST_TYPE_NORMAL;

}

@Override

public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View view;

if (viewType == LIST_TYPE_AD) {

view = View.inflate(AdvertisementImageActivity.this, R.layout.item1, null);

} else {

view = View.inflate(AdvertisementImageActivity.this, R.layout.item0, null);

RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

view.setLayoutParams(lp);

}

return new MyHolder(view);

}

@Override

public void onBindViewHolder(MyHolder holder, int position) {

if (position == 10) {

holder.windowImageView.bindRecyclerView(rv_content);

holder.windowImageView.setImageResource(R.drawable.timg2);

} else {

holder.itemView.setBackgroundColor(Color.rgb((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)));

}

}

@Override

public int getItemCount() {

return 20;

}

class MyHolder extends RecyclerView.ViewHolder {

AdvertisementView windowImageView;

MyHolder(View itemView) {

super(itemView);

windowImageView = itemView.findViewById(R.id.wiv);

}

}

}

主要的逻辑代码在AdvertisementView (继承自view) 中。

AdvertisementView.class

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

realWidth = measureHandle(getSuggestedMinimumWidth(), widthMeasureSpec);

realHeight = measureHandle(getSuggestedMinimumHeight(), heightMeasureSpec);

setMeasuredDimension(realWidth, realHeight);

createBitmap();

}

private int measureHandle(int defaultSize, int measureSpec) {

int result;

int specMode = View.MeasureSpec.getMode(measureSpec);

int specSize = View.MeasureSpec.getSize(measureSpec);

if (specMode == View.MeasureSpec.EXACTLY || specMode == View.MeasureSpec.AT_MOST) {

result = specSize;

} else {

result = defaultSize;

}

return result;

}

获取view的宽高,本来通过 getWidth()和getHeight()方法也能获取,只不过因为我需要在 onMeasure()中测量广告图的大小,这个时候的getWidth()和getHeight()返回为0,所以通过变量的形式实现。

createBitmap()函数

这个函数干了什么?

将广告图按照自定义View的宽度等比缩放

设置承载广告图画布的最大偏移量,即第一次出现时最大的偏移量

按照比例scale值计算画布的实际偏移量drawableDisY,并重绘

private void createBitmap() {

Resources resources = mContext.getResources();

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource(resources, resId, options);

// outWidth是以dp为单位的,需要做一次单位转化

int outWidthPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, options.outWidth, resources.getDisplayMetrics());

int outHeightPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, options.outHeight, resources.getDisplayMetrics());

scale = 1.0f * realWidth / outWidthPx;

int processedWidth = (int) (scale * outWidthPx);

processedHeight = (int) (scale * outHeightPx);

options.inSampleSize = calculateInSampleSize(outWidthPx, outHeightPx, processedWidth, processedHeight);

options.inJustDecodeBounds = false;

Bitmap sourceBitmap = BitmapFactory.decodeResource(resources, resId, options);

Matrix mMatrix = new Matrix();

mMatrix.postScale(scale, scale);

Bitmap targetBitmap = Bitmap.createBitmap(sourceBitmap, 0, 0, sourceBitmap.getWidth(), sourceBitmap.getHeight(), mMatrix, true);

targetDrawable = new BitmapDrawable(resources, targetBitmap);

sourceBitmap.recycle();

maxDistanceY = -targetBitmap.getHeight() + realHeight;

new Handler().post(new Runnable() {

@Override

public void run() {

getLocationInWindow(location);

drawableDisY = (recyclerLocation[1] - location[1]) * scale;

boundTop();

invalidate();

}

});

}

onDraw函数

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (resId == 0) return;

canvas.save();

canvas.translate(0, drawableDisY);

targetDrawable.setBounds(0, 0, realWidth, processedHeight);

targetDrawable.draw(canvas);

canvas.restore();

}

绘制视图,注意要保存画布,在视图绘制结束之后,还原画布。

通过setBounds()绘制广告图的原始大小,注意这里的图片不会受限于父类大小的影响

targetDrawable.setBounds(0, 0, realWidth, processedHeight);

接下来我们的广告图还要跟随recycleView的滚动而滚动。

public void bindRecyclerView(RecyclerView recyclerView) {

if (recyclerView == null || recyclerView.equals(this.recyclerView)) {

return;

}

this.recyclerView = recyclerView;

rvHeight = recyclerView.getLayoutManager().getHeight();

recyclerView.getLocationInWindow(recyclerLocation);

recyclerView.addOnScrollListener(rvScrollListener = new RecyclerView.OnScrollListener() {

@Override

public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

super.onScrolled(recyclerView, dx, dy);

if (getTopDistance() > 0 && getTopDistance() + getHeight() < rvHeight) {

drawableDisY += dy * scale;

boundTop();

invalidate();

}

}

});

}

监听recycleView的滚动距离,动态修改广告图画布的偏移量,这样看上去就能达到文章开头哪有的效果了。

补上剩余代码

//控制画布偏移量

private void boundTop() {

if (drawableDisY > 0) {

drawableDisY = 0;

}

if (drawableDisY < maxDistanceY) {

drawableDisY = maxDistanceY;

}

}

//获取广告图的Y轴距离

private int getTopDistance() {

getLocationInWindow(location);

return location[1] - recyclerLocation[1];

}

以上流程就能满足知乎广告的低配效果

-----------------------优化版本--------------------

低配版本有两个大问题

多次滑动之后,会发现滑动距离不准确

每次加载广告的时候有明显卡顿效果

关于第一个问题是因为每次加载广告图的时候,都有一个绑定recycleView的操作,而添加滑动监听也会创建多个对象。导致出现异常

recyclerView.addOnScrollListener(rvScrollListener = new RecyclerView.OnScrollListener() {

@Override

public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

super.onScrolled(recyclerView, dx, dy);

if (getTopDistance() > 0 && getTopDistance() + getHeight() < rvHeight) {

drawableDisY += dy * scale;

boundTop();

invalidate();

}

}

});

解决方法也很简单,在绑定之前加一个取消监听的操作即可

private void unbindRecyclerView() {

if (rvScrollListener != null) {

recyclerView.removeOnScrollListener(rvScrollListener);

}

recyclerView = null;

}

关于第二个问题,是因为广告图尺寸较大,放在主线程加载是有可能导致界面卡顿的,那这样干脆加一个辅助类来处理图片

DrawableHelper.java

public class DrawableHelper {

private Context mContext;

private AdvertisementImageView2 mView;

private Drawable targetDrawable;

private float scale;

private ProcessListener listener;

public DrawableHelper(Context mContext, AdvertisementImageView2 mView) {

this.mContext = mContext;

this.mView = mView;

}

public void setProcessListener(ProcessListener listener){

this.listener = listener;

}

public void createDrawable() {

new Thread(new Runnable() {

@Override

public void run() {

int resId = mView.getResourceId();

Resources resources = mContext.getResources();

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource(resources, resId, options);

// options.outWidth is dp, need do dp -> px

int outWidthPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, options.outWidth, resources.getDisplayMetrics());

int outHeightPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, options.outHeight, resources.getDisplayMetrics());

scale = 1.0f * mView.getRealWidth() / outWidthPx;

int processedWidth = (int) (scale * outWidthPx);

int processedHeight = (int) (scale * outHeightPx);

options.inSampleSize = calculateInSampleSize(outWidthPx, outHeightPx, processedWidth, processedHeight);

options.inJustDecodeBounds = false;

Bitmap sourceBitmap = BitmapFactory.decodeResource(resources, resId, options);

Matrix mMatrix = new Matrix();

mMatrix.postScale(scale, scale);

Bitmap targetBitmap = Bitmap.createBitmap(sourceBitmap, 0, 0, sourceBitmap.getWidth(), sourceBitmap.getHeight(), mMatrix, true);

targetDrawable = new BitmapDrawable(resources, targetBitmap);

sourceBitmap.recycle();

listener.ProcessFinish(processedWidth, processedHeight);

}

}).start();

}

public Drawable getTargetDrawable() {

return targetDrawable;

}

private int calculateInSampleSize(int sourceWidth, int sourceHeight, int reqWidth, int reqHeight) {

int inSampleSize = 1;

if (sourceWidth > reqWidth || sourceHeight > reqHeight) {

int halfWidth = sourceWidth / 2;

int halfHeight = sourceHeight / 2;

while ((halfWidth / inSampleSize > reqWidth)

&& (halfHeight / inSampleSize > reqHeight)) {

inSampleSize *= 2;

}

}

return inSampleSize;

}

public interface ProcessListener {

void ProcessFinish(int width, int height);

}

}

子线程处理图片,接口回调处理结果,自定义View中根据回调结果刷新界面

helper = new DrawableHelper(context, this);

helper.setProcessListener(new DrawableHelper.ProcessListener() {

@Override

public void ProcessFinish(int width, int height) {

rescaleHeight = height;

scale = 1.0f * height / rvHeight;

mMimDisPlayTop = -height + realHeight;

getLocationInWindow(location);

drawableDisY = (rvLocation[1] - location[1]) * scale;

boundTop();

post(new Runnable() {

@Override

public void run() {

invalidate();

}

});

}

});

要注意的是,回调结果也是在子线程中,所以需要post()方法发回UI线程。

相关链接

最后贴上代码地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值