【免费】安卓RecyclerView瀑布流效果实现资源-CSDN文库
1.WaterfallFlowActivity 主函数代码:?
package com.example.mytestapplication;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.example.mytestapplication.databinding.ActivityMainBinding;
import com.example.mytestapplication.databinding.ActivityTestBinding;
import com.example.mytestapplication.databinding.ActivityWaterfallFlowBinding;
import com.example.mytestapplication.pbl.FullyStaggeredGridLayoutManager;
import com.example.mytestapplication.pbl.RVAdapter;
import com.example.mytestapplication.pbl.RVBean;
import com.example.mytestapplication.ui.DemoAdapter;
import com.example.mytestapplication.ui.ImageUtil;
import java.util.ArrayList;
import java.util.List;
/**
* RecyclerView实现简单的瀑布流效果
*/
public class WaterfallFlowActivity extends AppCompatActivity {
private ActivityWaterfallFlowBinding binding;
private List<RVBean> rvBeanList = new ArrayList<>();
private RVAdapter adapter;
private final static String TAG = "DemoStaggerdRecyclerView";
private final static String CDN_URL="https://vd3.bdstatic.com/mda-pehiqe0dcmd4cry9/sc/cae_h264/1684592473466216903/mda-pehiqe0dcmd4cry9.mp4";
private final static String CDN_URL1="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F70c03ac511079c42b2ecddef0ff4444f846de67ebf58a-ZQ9m0f_fw658&refer=http%3A%2F%2Fhbimg.b0.upaiyun.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641973590&t=2c23fb6d6b200160666f0ccd81d7368a";
private final static String CDN_URL2="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2Fa3652914-9074-4c2d-ba91-7677c42a0cdf%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1687673320&t=ace2227ee8b9c0ac0aadaf49cbe25f1e";
private final static String CDN_URL3="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F5c2fde87-cfa6-4e76-893e-3f032cc41ce5%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1687441411&t=10b8d72201811e966f1404457a5371d2";
private final static String CDN_URL4 = "https://img.alicdn.com/tps/TB1uyhoMpXXXXcLXVXXXXXXXXXX-476-538.jpg_240x5000q50.jpg_.webp";
private final static String CDN_URL5 = "http://b247.photo.store.qq.com/psb?/V11ZojBI312o2K/63aY8a4M5quhi.78*krOo7k3Gu3cknuclBJHS3g1fpc!/b/dDXWPZMlBgAA";
private final static String CDN_URL6 = "https://img0.baidu.com/it/u=2746042376,2078414564&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=433";
private final static String CDN_URL7 = "https://s1.chu0.com/src/img/gif/52/52784937bc374c779332150318a75baf.gif?imageMogr2/auto-orient/thumbnail/!231x201r/gravity/Center/crop/231x201/quality/85/&e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:SXbWiapi5fG0MM5V-0hwE43cvnY=";
private final static String CDN_URL8 = "https://s1.chu0.com/src/img/gif/60/606e2efad8ea4417a4e101fa1285d609.gif?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:IA5gbzlKc-NNfpArFhy-5xGKjUg=";
private final static String CDN_URL9 = "http://contentcms-bj.cdn.bcebos.com/cmspic/dd7b0d8aa276e3a062edf462b4082065.jpeg";
private final static String CDN_URL10 = "";
private final static String CDN_URL11 = "";
private final static String CDN_URL12 = "";
private final static String CDN_URL13 = "";
private final static String CDN_URL14 = "";
private FullyStaggeredGridLayoutManager slm=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.activity_waterfall_flow, null, false);
setContentView(binding.getRoot());
slm=new FullyStaggeredGridLayoutManager(2,
FullyStaggeredGridLayoutManager.VERTICAL);
binding.rv.setLayoutManager(slm);
((SimpleItemAnimator)binding.rv.getItemAnimator()).setSupportsChangeAnimations(false);
((DefaultItemAnimator) binding.rv.getItemAnimator()).setSupportsChangeAnimations(false);
binding.rv.getItemAnimator().setChangeDuration(0);
binding.rv.setHasFixedSize(true);
initData();
}
private void initData() {
rvBeanList.add(new RVBean(CDN_URL, "1"));
rvBeanList.add(new RVBean(CDN_URL1, "2"));
rvBeanList.add(new RVBean(CDN_URL2, "3"));
rvBeanList.add(new RVBean(CDN_URL3, "4"));
rvBeanList.add(new RVBean(CDN_URL4, "5"));
rvBeanList.add(new RVBean(CDN_URL1, "6"));
rvBeanList.add(new RVBean(CDN_URL, "7"));
rvBeanList.add(new RVBean(CDN_URL1, "8"));
rvBeanList.add(new RVBean(CDN_URL, "9"));
rvBeanList.add(new RVBean(CDN_URL1, "10"));
rvBeanList.add(new RVBean(CDN_URL, "11"));
rvBeanList.add(new RVBean(CDN_URL8, "12"));
adapter = new RVAdapter(this, rvBeanList);
//主要就是这个LayoutManager,就是用这个来实现瀑布流的,2表示有2列(垂直)或3行(水平),我们这里用的垂直VERTICAL
//binding.rv.addItemDecoration(new SpaceItemDecoration(2, 20));
binding.rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!recyclerView.canScrollVertically(1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
Log.i(TAG, "上拉拉不动时触发加载新数据");
rvBeanList = new ArrayList<>();
rvBeanList.add(new RVBean(CDN_URL, "13"));
rvBeanList.add(new RVBean(CDN_URL1, "14"));
rvBeanList.add(new RVBean(CDN_URL, "15"));
rvBeanList.add(new RVBean(CDN_URL1, "16"));
rvBeanList.add(new RVBean(CDN_URL, "17"));
rvBeanList.add(new RVBean(CDN_URL1, "18"));
rvBeanList.add(new RVBean(CDN_URL6, "19"));
rvBeanList.add(new RVBean(CDN_URL6, "20"));
rvBeanList.add(new RVBean(CDN_URL7, "21"));
rvBeanList.add(new RVBean(CDN_URL8, "22"));
rvBeanList.add(new RVBean(CDN_URL, "23"));
rvBeanList.add(new RVBean(CDN_URL, "24"));
rvBeanList.add(new RVBean(CDN_URL1, "25"));
rvBeanList.add(new RVBean(CDN_URL, "26"));
rvBeanList.add(new RVBean(CDN_URL1, "27"));
rvBeanList.add(new RVBean(CDN_URL, "28"));
rvBeanList.add(new RVBean(CDN_URL1, "29"));
rvBeanList.add(new RVBean(CDN_URL1, "30"));
rvBeanList.add(new RVBean(CDN_URL6, "31"));
rvBeanList.add(new RVBean(CDN_URL7, "32"));
rvBeanList.add(new RVBean(CDN_URL8, "33"));
rvBeanList.add(new RVBean(CDN_URL1, "34"));
rvBeanList.add(new RVBean(CDN_URL6, "35"));
adapter.refreshDatas(rvBeanList);
}
if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
Log.i(TAG, "下拉拉不动时触发加载新数据");
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
slm.invalidateSpanAssignments();//防止第一行到顶部有空白
}
});
//((SimpleItemAnimator)RecyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
binding.rv.setAdapter(adapter);
//使用接口实现对应子控件中的监听
adapter.setOnCardItemClickListener(new RVAdapter.CardListener() {
@Override
public void setCardClickListener(int num) {
Toast.makeText(WaterfallFlowActivity.this, "这是第"+num+"个ITEM", Toast.LENGTH_SHORT).show();
}
});
}
}
2.适配器:
package com.example.mytestapplication.pbl;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.example.mytestapplication.R;
import com.example.mytestapplication.databinding.RvItemBinding;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
public class RVAdapter extends RecyclerView.Adapter<RVAdapter.VH> {
private Context context;
private List<RVBean> rvBeans;
CardListener cardListener;
private final static String TAG = "DemoStaggerdRecyclerView";
public RVAdapter(Context context, List<RVBean> rvBeans) {
this.context = context;
this.rvBeans = rvBeans;
}
@Override
public int getItemViewType(int position) {
return position;
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new VH(DataBindingUtil.inflate(
LayoutInflater.from(context), R.layout.rv_item, parent, false).getRoot());
}
@Override
public void onBindViewHolder(@NonNull VH holder, @SuppressLint("RecyclerView") int position) {
//try {
RvItemBinding binding = DataBindingUtil.bind(holder.itemView);
//binding.rvTextView.setText(rvBeans.get(position).getText());
binding.setItem(rvBeans.get(position));
/*
//Set size
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//这个参数设置为true才有效,
Bitmap bmp = BitmapFactory.decodeFile(rvBeans.get(position).getUrl(), options);
//这里的bitmap是个空
int outHeight = options.outHeight;
int outWidth = options.outWidth;
Glide.with(context).load(rvBeans.get(position).getUrl()).override(outWidth,
outHeight).into(binding.rvImageView);
} catch (Exception e) {
Log.e(TAG, ">>>>>>onbindViewHolder error: " + e.getMessage(), e);
}
*/
binding.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(cardListener != null){
cardListener.setCardClickListener(position);
}
}
});
}
@Override
public int getItemCount() {
return rvBeans.size();
}
public class VH extends RecyclerView.ViewHolder {
public VH(@NonNull View itemView) {
super(itemView);
}
}
//增加外部调用增加一条记录
public void refreshDatas(List<RVBean> datas) {
int pc=0;
if (datas != null && datas.size() > 0) {
int oldSize = rvBeans.size();
//List<RVBean> refreshedData = new ArrayList<RVBean>();
boolean isItemExisted = false;
for (Iterator<RVBean> newData = datas.iterator(); newData.hasNext(); ) {
RVBean a = newData.next();
for (Iterator<RVBean> existedData = rvBeans.iterator(); existedData.hasNext(); ) {
RVBean b = existedData.next();
if (b.equals(a)) {
{
isItemExisted = true;
//Log.i(TAG, b.getText() + " -> " + b.getUrl() + " is existed");
break;
}
}
}
if (!isItemExisted) {
pc+=1;
rvBeans.add(a);
}
}
Log.i(TAG,">>>>>>pc->"+pc);
if(pc>0){
notifyItemRangeChanged(oldSize,rvBeans.size());
}
}
}
public static interface CardListener{
public void setCardClickListener(int num);
}
public void setOnCardItemClickListener(CardListener mCardListener) {
cardListener = mCardListener;
}
}
3.自定义FullyStaggeredGridLayoutManager:
package com.example.mytestapplication.pbl;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import java.lang.reflect.Field;
/**
* @descride 解决Scrollview中嵌套RecyclerView实现瀑布流时无法显示的问题,同时修复了子View显示时底部多出空白区域的问题
*/
public class FullyStaggeredGridLayoutManager extends StaggeredGridLayoutManager {
private static boolean canMakeInsetsDirty = true;
private static Field insetsDirtyField = null;
private static final int CHILD_WIDTH = 0;
private static final int CHILD_HEIGHT = 1;
private static final int DEFAULT_CHILD_SIZE = 100;
private int spanCount = 0;
private final int[] childDimensions = new int[2];
private int[] childColumnDimensions;
private int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
private final Rect tmpRect = new Rect();
public FullyStaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public FullyStaggeredGridLayoutManager(int spanCount, int orientation) {
super(spanCount, orientation);
this.spanCount = spanCount;
}
public static int makeUnspecifiedSpec() {
return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,
int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;
final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;
final int unspecified = makeUnspecifiedSpec();
if (exactWidth && exactHeight) {
// in case of exact calculations for both dimensions let's use default "onMeasure" implementation
super.onMeasure(recycler, state, widthSpec, heightSpec);
return;
}
final boolean vertical = getOrientation() == VERTICAL;
initChildDimensions(widthSize, heightSize, vertical);
int width = 0;
int height = 0;
// it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
// happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
// recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
// called whiles scrolling)
recycler.clear();
final int stateItemCount = state.getItemCount();
final int adapterItemCount = getItemCount();
childColumnDimensions = new int[adapterItemCount];
// adapter always contains actual data while state might contain old data (f.e. data before the animation is
// done). As we want to measure the view with actual data we must use data from the adapter and not from the
// state
for (int i = 0; i < adapterItemCount; i++) {
if (vertical) {
if (!hasChildSize) {
if (i < stateItemCount) {
// we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
// we will use previously calculated dimensions
measureChild(recycler, i, widthSize, unspecified, childDimensions);
} else {
logMeasureWarning(i);
}
}
childColumnDimensions[i] = childDimensions[CHILD_HEIGHT];
//height += childDimensions[CHILD_HEIGHT];
if (i == 0) {
width = childDimensions[CHILD_WIDTH];
}
if (hasHeightSize && height >= heightSize) {
break;
}
} else {
if (!hasChildSize) {
if (i < stateItemCount) {
// we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
// we will use previously calculated dimensions
measureChild(recycler, i, unspecified, heightSize, childDimensions);
} else {
logMeasureWarning(i);
}
}
width += childDimensions[CHILD_WIDTH];
if (i == 0) {
height = childDimensions[CHILD_HEIGHT];
}
if (hasWidthSize && width >= widthSize) {
break;
}
}
}
int[] maxHeight = new int[spanCount];
for (int i = 0; i < adapterItemCount; i++) {
int position = i % spanCount;
if (i < spanCount) {
maxHeight[position] += childColumnDimensions[i];
} else if (position < spanCount) {
int mixHeight = maxHeight[0];
int mixPosition = 0;
for (int j = 0; j < spanCount; j++) {
if (mixHeight > maxHeight[j]) {
mixHeight = maxHeight[j];
mixPosition = j;
}
}
maxHeight[mixPosition] += childColumnDimensions[i];
}
}
for (int i = 0; i < spanCount; i++) {
for (int j = 0; j < spanCount - i - 1; j++) {
if (maxHeight[j] < maxHeight[j + 1]) {
int temp = maxHeight[j];
maxHeight[j] = maxHeight[j + 1];
maxHeight[j + 1] = temp;
}
}
}
height = maxHeight[0];//this is max height
if (exactWidth) {
width = widthSize;
} else {
width += getPaddingLeft() + getPaddingRight();
if (hasWidthSize) {
width = Math.min(width, widthSize);
}
}
if (exactHeight) {
height = heightSize;
} else {
height += getPaddingTop() + getPaddingBottom();
if (hasHeightSize) {
height = Math.min(height, heightSize);
}
}
setMeasuredDimension(width, height);
}
private void logMeasureWarning(int child) {
// if (BuildConfig.DEBUG) {
// Log.w("LinearLayoutManager", "Can't measure child #"
// + child
// + ", previously used dimensions will be reused."
// + "To remove this message either use #setChildSize() method or don't run RecyclerView animations");
// }
}
private void initChildDimensions(int width, int height, boolean vertical) {
if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
// already initialized, skipping
return;
}
if (vertical) {
childDimensions[CHILD_WIDTH] = width;
childDimensions[CHILD_HEIGHT] = childSize;
} else {
childDimensions[CHILD_WIDTH] = childSize;
childDimensions[CHILD_HEIGHT] = height;
}
}
@Override public void setOrientation(int orientation) {
// might be called before the constructor of this class is called
//noinspection ConstantConditions
if (childDimensions != null) {
if (getOrientation() != orientation) {
childDimensions[CHILD_WIDTH] = 0;
childDimensions[CHILD_HEIGHT] = 0;
}
}
super.setOrientation(orientation);
}
public void clearChildSize() {
hasChildSize = false;
setChildSize(DEFAULT_CHILD_SIZE);
}
public void setChildSize(int childSize) {
hasChildSize = true;
if (this.childSize != childSize) {
this.childSize = childSize;
requestLayout();
}
}
private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize,
int heightSize, int[] dimensions) {
final View child;
try {
child = recycler.getViewForPosition(position);
} catch (IndexOutOfBoundsException e) {
// if (BuildConfig.DEBUG) {
// Log.w("LinearLayoutManager",
// "LinearLayoutManager doesn't work well with animations. Consider switching them off",
// e);
// }
return;
}
final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();
final int hPadding = getPaddingLeft() + getPaddingRight();
final int vPadding = getPaddingTop() + getPaddingBottom();
final int hMargin = p.leftMargin + p.rightMargin;
final int vMargin = p.topMargin + p.bottomMargin;
// we must make insets dirty in order calculateItemDecorationsForChild to work
makeInsetsDirty(p);
// this method should be called before any getXxxDecorationXxx() methods
calculateItemDecorationsForChild(child, tmpRect);
final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);
final int childWidthSpec =
getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width,
canScrollHorizontally());
final int childHeightSpec =
getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height,
canScrollVertically());
child.measure(childWidthSpec, childHeightSpec);
dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;
// as view is recycled let's not keep old measured values
makeInsetsDirty(p);
recycler.recycleView(child);
}
private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
if (!canMakeInsetsDirty) {
return;
}
try {
if (insetsDirtyField == null) {
insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
insetsDirtyField.setAccessible(true);
}
insetsDirtyField.set(p, true);
} catch (NoSuchFieldException e) {
onMakeInsertDirtyFailed();
} catch (IllegalAccessException e) {
onMakeInsertDirtyFailed();
}
}
private static void onMakeInsertDirtyFailed() {
canMakeInsetsDirty = false;
// if (BuildConfig.DEBUG) {
// Log.w("LinearLayoutManager",
// "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
// }
}
}
4.布局:
activity_waterfall_flow
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
</layout>
rv_item
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="item"
type="com.example.mytestapplication.pbl.RVBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/rvImageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitXY"
app:url="@{item.url}" />
<TextView
android:id="@+id/rvTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:layout_margin="4dp"
android:text="@{item.text}" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</layout>
5.build.gradle
plugins {
id("com.android.application")
}
android {
namespace = "com.example.mytestapplication"
compileSdk = 33
dataBinding {
enable = true
}
defaultConfig {
applicationId = "com.example.mytestapplication"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.8.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("androidx.navigation:navigation-fragment:2.5.3")
implementation("androidx.navigation:navigation-ui:2.5.3")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation ("com.github.bumptech.glide:glide:4.12.0")
implementation ("androidx.palette:palette:1.0.0")
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--允许Glide监视连接状态,并在用户从断开连接到已连接网络的状态。-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyTestApplication"
tools:targetApi="31">
<activity
android:name=".WaterfallFlowActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
记得添加网络权限!
package com.example.mytestapplication.pbl;
import android.widget.ImageView;
import androidx.databinding.BindingAdapter;
import com.bumptech.glide.Glide;
import java.util.Objects;
public class RVBean {
private String url;
private String text;
private final static String TAG = "DemoStaggerdRecyclerView";
@BindingAdapter("url")
public static void loadImg(ImageView imageView, String url) {
Glide.with(imageView.getContext()).load(url).into(imageView);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public RVBean(String url, String text) {
this.url = url;
this.text = text;
}
@Override
public boolean equals(Object o) {
if (this == o) {
//Log.i(TAG, ">>>>>>this==o return true");
return true;
}
if (o == null || getClass() != o.getClass()) {
//Log.i(TAG, ">>>>>>o==null||getClass()!=o.getClass() is false");
return false;
}
RVBean rvBean = (RVBean) o;
if (rvBean.url.length() != url.length() || rvBean.text.length() != text.length()) {
//Log.i(TAG, ">>>>>>target length()!=existed url length");
return false;
}
if(url.equals(rvBean.url)&&text.equals(rvBean.text)){
//Log.i(TAG,">>>>>>url euqlas && text equals");
return true;
}else{
//Log.i(TAG,">>>>>>not url euqlas && text equals");
return false;
}
}
@Override
public int hashCode() {
int hashCode = Objects.hash(url, text);
//Log.i(TAG, ">>>>>>hashCode->" + hashCode);
return hashCode;
}
}