Android 一分钟使用RecyclerView完美实现瀑布

【免费】安卓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;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/255144.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

头部游戏厂商鸿蒙合作,开发岗又‘缺人‘

12月18日&#xff0c;米哈游宣布将基于HarmonyOS NEXT启动鸿蒙原生应用开发&#xff0c;成为又一家启动鸿蒙原生应用开发的头部游戏厂商。 作为一家创立于2011年的科技型文创企业&#xff0c;上海米哈游网络科技股份有限公司推出了众多高品质人气产品&#xff0c;其中包括《崩坏…

[GXYCTF2019]Ping Ping Ping (文件执行漏洞)

本题考点&#xff1a; 1、命令联合执行 2、命令绕过空格方法 3、变量拼接 1、命令联合执行 ; 前面的执行完执行后面的| 管道符&#xff0c;上一条命令的输出&#xff0c;作为下一条命令的参数&#xff08;显示后面的执行结果&#xff09;|| 当前面的执行出错时&#xff08;为…

波奇学Linux:进程等待

僵尸进程(Z状态)无法被kill指令杀死&#xff0c;通过进程等待杀掉它&#xff0c;解决内存泄漏问题&#xff08;进程处于僵尸态&#xff0c;仍然维护pcb结构体来解决问题&#xff09; 通过进程等待&#xff0c;获得进程退出情况 wait回收僵尸态进程 我们可以看到进程由五秒后子…

工业应用新典范,飞凌嵌入式FET-D9360-C核心板发布!

来源&#xff1a;飞凌嵌入式官网 当前新一轮科技革命和产业变革突飞猛进&#xff0c;工业领域对高性能、高可靠性、高稳定性的计算需求也在日益增长。为了更好地满足这一需求&#xff0c;飞凌嵌入式与芯驰科技&#xff08;SemiDrive&#xff09;强强联合&#xff0c;基于芯驰D9…

机器视觉【1】相机的成像(畸变)模型

零、前言 很久没写文章&#xff0c;简单唠一唠。 不知道巧合还是蜀道同归&#xff0c;部门领导设定了些研究课题&#xff0c;用于公司部门员工的超前发展&#xff0c;该课题是“2D to 3D的三维重建”&#xff0c;这一块刚好是我个人看中的一个大方向&#xff0c;所以就有了这…

【03】GeoScene创建海图或者电子航道图数据

1 配置Nautical属性 1.1 管理长名称 长名称&#xff08;LNAM&#xff09;是一个必要的对象标识符&#xff0c;是生产机构&#xff08;AGEN&#xff09;、要素识别号码&#xff08;FIDN&#xff09;和要素识别子项&#xff08;FIDS&#xff09;组件的串联。这三个子组件用于数…

linux性能优化-cpu使用率

文章目录 1.CPU使用率2.节拍率的概念2.1.查看系统节拍率2.2.用户节拍率2.3.CPU使用率公式 3.怎么查看CPU使用率3.1.top显示系统总体CPU使用情况3.2.pidstat分析每个进程CPU使用情况 4.CPU使用率过高怎么办4.1.perf命令详解 1.CPU使用率 用什么指标来描述系统的CPU性能呢?不是…

深度学习——AlexNet

论文信息 论文名称&#xff1a;ImageNet Classification with Deep Convolutional Neural Networks 论文别名&#xff1a;AlexNet 发表期刊&#xff1a;NIPS 论文地址: https://www.cin.ufpe.br/~rmd2/ImageNet%20classification%20wth%20deep%20convolutional%20neural%20net…

蚂蚁SEO强引蜘蛛是什么

强引蜘蛛在网页中是指一些特殊类型的网页&#xff0c;这些网页具有极高的吸引力和价值&#xff0c;能够吸引搜索引擎蜘蛛&#xff08;Spider&#xff09;的强烈关注和抓取。强引蜘蛛的网页通常具有以下特点&#xff1a; 如何联系蚂蚁seo&#xff1f; baidu搜索&#xff1a;如…

Python开发GUI常用库PyQt6和PySide6介绍之一:概述

Python开发GUI常用库PyQt6和PySide6介绍之一&#xff1a;概述 Python开发GUI有许多选择&#xff0c;下面是常见的选择&#xff1a; Tkinter&#xff1a;Tkinter是Python标准库中的一个GUI工具包&#xff0c;易于学习和使用。它提供了丰富的组件和布局选项&#xff0c;适用于简…

后端相关随机题目记录(1)

目录 后端相关随机题目记录&#xff08;1&#xff09; 后端相关随机题目记录&#xff08;1&#xff09;Bean的类型以及作用域Bean的生命周期Mysql的底层数据结构RedisHttp和Https区别AOP在项目的应用 自定义注解&#xff1f;请求在spring中的一个流程Nacos与zk的区别SpringMV…

那些年项目中踩的那些坑(二)

目录 一、硬件资源与软件需求不匹配1.1 背景1.2教训 一、硬件资源与软件需求不匹配 1.1 背景 在项目中期需要添加XCP到TDA4的main域中&#xff0c;但是发现所有的八路can中已经有七路can被占用&#xff0c;剩下一路因为没有TJA1045驱动无法使用。 1.2教训 1.软件架构缺失&am…

FPGA时序分析与时序约束(二)——时钟约束

目录 一、时序约束的步骤 二、时序网表和路径 2.1 时序网表 2.2 时序路径 三、时序约束的方式 三、时钟约束 3.1 主时钟约束 3.2 虚拟时钟约束 3.3 衍生时钟约束 3.4 时钟组约束 3.5 时钟特性约束 3.6 时钟延时约束 一、时序约束的步骤 上一章了解了时序分析和约束…

高级桌面编程(一)

前言 学习心得&#xff1a;C# 入门经典第8版书中的第15章《高级桌面编程》 创建控件并设置样式 1 样式 Style WPF 当中我们可以对每一个控件进行完全的自定义。我们可以随意更改控件外观和功能。提供我们能完成这样的效果与控件的样式&#xff08;Style&#xff09;有着不可分…

如何实现TensorFlow自定义算子?

在上一篇文章中 Embedding压缩之基于二进制码的Hash Embedding&#xff0c;提供了二进制码的tensorflow算子源码&#xff0c;那就顺便来讲下tensorflow自定义算子的完整实现过程。 前言 制作过程基于tensorflow官方的custom-op仓库以及官网教程&#xff0c;并且在Ubuntu和Mac…

第8次实验:UDP

目的&#xff1a; 来看一下UDP&#xff08;用户数据报协议&#xff09;的细节。UDP是整个互联网上使用的一种传输协议。在不需要可靠性的情况下&#xff0c;作为TCP的替代品在互联网上使用。它在你的课文的第6.4节中有所涉及。在做这个实验之前&#xff0c;先复习一下这一部分 …

【精选】计算机网络教程(第7章网络安全)

目录 前言 第7章网络安全 1、公钥 2、私钥 3、数字签名 前言 总结计算机网络教程课程期末必记知识点。 第7章网络安全 1、公私密钥和对称密钥 公私密钥&#xff08;或非对称密钥&#xff09;和对称密钥是在密码学中用于加密和解密数据的两种不同的密钥类型。 公私密钥…

MySQL主从复制详解

目录 1. 主从复制的工作原理 1.1. 主从复制的角色 1.2. 主从复制的流程 2. 配置MySQL主从复制 2.1. 确保主服务器开启二进制日志 2.2. 设置从服务器 2.3. 连接主从服务器 2.4. 启动复制 3. 主从复制的优化与注意事项 3.1. 优化复制性能 3.2. 注意复制延迟 3.3. 处理…

Leetcode 376 摆动序列

题意理解&#xff1a; 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 如果是摆动序列&#xff0c;前后差值呈正负交替出现 为保证摆动序列尽可能的长&#xff0c;我们可以尽可能的保留峰值&#xff0c;&#xff0c;删除上下坡的中间值&…

2023.12.17 关于 Redis 的特性和应用场景

目录 引言 Redis 特性 内存中存储数据 可编程性 可扩展性 持久化 支持集群 高可用性 Redis 优势 Redis 用作数据库 Redis 相较于 MySQL 优势 Redis 相较于 MySQL 劣势 Redis 用作缓存 典型场景 Redis 存储 session 信息 Redis 用作消息队列 初心 消息队列的…
最新文章