翻看腾讯新闻App时,发现有个广告UI交互挺好玩的。先看图
gif图片可能有点卡,从视觉上看,有点像底部放了张海报,上层列表有个窗口透视过去,随着滚动窗口看到底部海报不同部分。
撸起袖子开干。
一般自定义View相关的,第一步就是做拆解。首先,如果从排版看,最外层View不是RecyclerView就是ListView
方案一
如图:底部是一个ImageView,上层是一个列表RecyclerView,设置列表背景为透明的,根据item type加载不同ViewHolder,当需要展示底部广告时,其ViewHolder itemView背景为透明的,即可满足UI的效果。
不足:布局不够简化,数据填充不灵活,如果屏幕内出现多个相关类型的窗口时,不满足加载不同的海报。
方案二
如果广告窗口是ImageView,按照大图的局部加载显示思想,当窗口ViewHolder向上滑,图片显示下部分区域,反之,窗口ViewHolder往下滑时,图片显示上部分。
代码实现:
public class ScrollWindowImageView extends AppCompatImageView {
private boolean initialized;
private Matrix matrix;
private boolean isScrollDown = true;
private float translate;
public ScrollWindowImageView(Context context) {
this(context, null);
}
public ScrollWindowImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrollWindowImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setScaleType(ScaleType.MATRIX);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initialized = true;
initImage();
}
private void initImage() {
final Drawable drawable = getDrawable();
if (!initialized || drawable == null) {
return;
}
final int viewWidth = getWidth();
final int viewHeight = getHeight();
final int drawableWidth = drawable.getIntrinsicWidth();
final int drawableHeight = drawable.getIntrinsicHeight();
float scaleX = (float) viewWidth / drawableWidth;
matrix = getImageMatrix();
matrix.setScale(scaleX, scaleX);
matrix.postTranslate(0, -(drawableHeight * scaleX - viewHeight));
setImageMatrix(matrix);
}
public void scrollWindow(float scrollY) {
final Drawable drawable = getDrawable();
if (!initialized || drawable == null) {
return;
}
float[] imageMatrixValues = new float[9];
matrix.getValues(imageMatrixValues);
float scale = Math.abs(imageMatrixValues[0]) + Math.abs(imageMatrixValues[1]);
matrix.setScale(scale, scale);
float translate = scrollY - (drawable.getIntrinsicHeight() * scale - getHeight());
if (translate > 0) {
translate = 0;
}
matrix.postTranslate(0, translate);
setImageMatrix(matrix);
}
private final Runnable runnable = new Runnable() {
@Override
public void run() {
if (translate > getDrawable().getIntrinsicHeight() - DensityUtil.dp2px(getContext(), 200)) {
isScrollDown = false;
} else if (translate < -DensityUtil.dp2px(getContext(), 200)) {
isScrollDown = true;
}
if (isScrollDown) {
translate += 10;
} else {
translate -= 10;
}
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) getLayoutParams();
params.topMargin = (int) translate;
setLayoutParams(params);
scrollWindow(-translate);
HandlerUtil.runOnUiThreadDelay(this, 16);
}
};
public void show() {
HandlerUtil.runOnUiThreadDelay(runnable, 16);
}
}
接下来就是拼接了,ScrollWindowImageView作为RecyclerView ViewHolder的ItemView,当滑动时,将滑动响应传递给ScrollWindowImageView,若向上滑,ImageView图像Bitmap往下移动
public class ScrollWindowImageView extends AppCompatImageView {
private boolean initialized;
private Matrix matrix;
public ScrollWindowImageView(Context context) {
this(context, null);
}
public ScrollWindowImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrollWindowImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setScaleType(ScaleType.MATRIX);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initialized = true;
initImage();
}
private void initImage() {
final Drawable drawable = getDrawable();
if (!initialized || drawable == null) {
return;
}
final int viewWidth = getWidth();
final int viewHeight = getHeight();
final int drawableWidth = drawable.getIntrinsicWidth();
final int drawableHeight = drawable.getIntrinsicHeight();
float scaleX = (float) viewWidth / drawableWidth;
matrix = getImageMatrix();
matrix.setScale(scaleX, scaleX);
matrix.postTranslate(0, -(drawableHeight * scaleX - viewHeight));
setImageMatrix(matrix);
}
public void scrollWindow(float scrollY) {
final Drawable drawable = getDrawable();
if (!initialized || drawable == null) {
return;
}
float[] imageMatrixValues = new float[9];
matrix.getValues(imageMatrixValues);
float scale = Math.abs(imageMatrixValues[0]) + Math.abs(imageMatrixValues[1]);
matrix.setScale(scale, scale);
float translate = scrollY - (drawable.getIntrinsicHeight() * scale - getHeight());
if (translate > 0) {
translate = 0;
}
matrix.postTranslate(0, translate);
setImageMatrix(matrix);
}
}
public class ScrollWindowActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scroll_window);
RecyclerView recyclerView = findViewById(R.id.scroll_window_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
ScrollWindowAdapter adapter = new ScrollWindowAdapter(this);
recyclerView.setAdapter(adapter);
recyclerView.addOnScrollListener(scrollListener);
adapter.notifyDataSetChanged();
}
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int childCount = layoutManager.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = layoutManager.getChildAt(i);
Object tag = view.getTag();
if (tag instanceof ScrollWindowViewHolder) {
ScrollWindowViewHolder holder = (ScrollWindowViewHolder) tag;
holder.onScrolled(recyclerView, view, dx, dy);
}
}
}
};
static class ScrollWindowAdapter extends RecyclerView.Adapter {
private LayoutInflater inflater;
ScrollWindowAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == 1) {
return new ScrollWindowViewHolder(inflater.inflate(R.layout.item_scroll_ad_window, parent, false));
}
return new NormalViewHolder(inflater.inflate(R.layout.item_scroll_normal, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (position == 6) {
((ScrollWindowViewHolder) holder).imageView.setImageResource(R.mipmap.car);
}
}
@Override
public int getItemCount() {
return 21;
}
@Override
public int getItemViewType(int position) {
if (position == 6) {
return 1;
}
return 0;
}
}
static class NormalViewHolder extends RecyclerView.ViewHolder {
NormalViewHolder(@NonNull View itemView) {
super(itemView);
}
}
static class ScrollWindowViewHolder extends RecyclerView.ViewHolder {
private ScrollWindowImageView imageView;
ScrollWindowViewHolder(@NonNull View itemView) {
super(itemView);
itemView.setTag(this);
imageView = itemView.findViewById(R.id.ad_window_image_view);
}
void onScrolled(RecyclerView recyclerView, View itemView, int dx, int dy) {
int parentBottom = recyclerView.getBottom();
int itemBottom = itemView.getBottom();
if (itemBottom < parentBottom) {
int scrollY = parentBottom - itemBottom;
imageView.scrollWindow(scrollY);
}
}
}
}
此方案支持屏幕内有多个item为广告位,数据配置填充灵活
方案和实现都比较简单,不过这种交互体验还是挺好的。