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

news2024/11/22 2:29:28

【免费】安卓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.coloradmin.cn/o/1320433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vs code调试.so文件

使用vs code调试.so文件 1 vs code中安装c的debug插件2 【重要】编写launch.json3 在.so的源码中打断点4 debug模式启动进程5 attach进程6 开始调试 .so是一种动态链接库&#xff0c;在大型项目以及跨语言项目中经常用到。在拿到.so文件对应的源码后进行debug呢&#xff1f; 简…

天猫数据分析平台-天猫销售数据查询软件-11月天猫平台冲锋衣市场销售运营数据分析

随着气温逐渐下降&#xff0c;保暖服饰迎来热销&#xff0c;冲锋衣的需求大增。如今冲锋衣已经不仅仅是户外运动的装备&#xff0c;还成为很多年轻人的日常穿搭和时尚的追求。 新的穿搭趋势也带来了巨大的市场机会。据公开数据显示&#xff0c;中国有冲锋衣生产及经营企业超过8…

SpringBoot之响应案例的详细解析

2.3 案例 下面我们通过一个案例&#xff0c;来加强对请求响应的学习。 2.3.1 需求说明 需求&#xff1a;加载并解析xml文件中的数据&#xff0c;完成数据处理&#xff0c;并在页面展示 获取员工数据&#xff0c;返回统一响应结果&#xff0c;在页面渲染展示 2.3.2 准备工作…

Missing artifact org.wltea.analyzer:ik-analyzer:jar:5.0

没有找到【org.wltea.analyzer】 找到了【org.wltea.ik-analyzer】 https://github.com/wks/ik-analyzer https://github.com/wks/ik-analyzer.git https://code.google.com/archive/p/ik-analyzer/downloads?page2 C:\Users\Administrator\Desktop\ik-analyzer-master>m…

LabVIEW软件模拟氢燃料电池在车辆中的应用

LabVIEW软件模拟氢燃料电池在车辆中的应用 在追求可持续能源的时代&#xff0c;氢燃料电池在绿色经济中扮演着关键角色。本研究通过LabVIEW软件模拟和评估了氢燃料电池在车辆应用中的性能和效率。LabVIEW作为一个强大的模拟工具&#xff0c;能够动态模拟氢燃料电池系统在不同条…

Hugging Face实战-系列教程19:文本摘要建模实战1 之 数据清洗(中文商城评价数据处理方法)

&#x1f6a9;&#x1f6a9;&#x1f6a9;Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 文本摘要建模实战1 之 数据清洗 文本摘要建模实战2 之 Tokenizer处理 1 任务概述 1.1 任…

『踩坑记录』IDEA Spring initialzr新建Spring项目不能选择jdk8的解决方法

问题描述 Spring initializr新建Spring项目不能选低版本java 解决方法 默认官方start.spring.io已不支持自动生成低版本jkd的Spring项目&#xff0c;自定义用阿里云的starter即可 用阿里云的就能支持低版本jdk了 完 欢迎关注我的CSDN博客 &#xff1a;Ho1aAs 版权属于&a…

【C++】POCO学习总结(十九):哈希、URL、UUID、配置文件、日志配置、动态库加载

【C】郭老二博文之&#xff1a;C目录 1、哈希 1.1 说明 std::map和std::set 的性能是&#xff1a;O(log n) POCO哈希的性能比STL容器更好&#xff0c;大约快两&#xff1b; POCO中对应std::map的是&#xff1a;Poco::HashMap&#xff1b; POCO中对应std::set的是 Poco::Hash…

【04】GeoScene导出海图或者电子航道图000数据成果

1创建一个带有覆盖面和定义的产品 如果你没有已存在的S-57数据&#xff0c;你可以通过捕捉新的产品覆盖范围&#xff08;多边形产品范围&#xff09;及其所需的产品定义信息&#xff08;产品元数据&#xff09;来为新产品创建基础。 注&#xff1a; 如果你已经有一个S-57数据…

3800个字彻底弄清cortex

3800个字彻底弄清cortex arm内核发展历史cortexM0系列芯片系统框图通用寄存器m0特殊寄存器m3/m4/m7特殊寄存器 MSP和PSPxPSRPRIMASKCONTROLFAULTMASKBASEPRI 栈空间操作异常和中断 系统异常 NVIC可嵌套向量中断控制器系统操作寄存器 NVIC寄存器系统控制块SCB寄存器SysTick寄存…

算法训练第四十一天|343. 整数拆分、96. 不同的二叉搜索树

343. 整数拆分&#xff1a; 题目链接 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 : 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。解答&…

银行测试:第三方支付平台业务流,功能/性能/安全测试方法(超详细整理)

1、第三方支付平台的功能和结构特点 在信用方面&#xff0c;第三方支付平台作为中介&#xff0c;在网上交易的商家和消费者之间作一个信用的中转&#xff0c;通过改造支付流程来约束双方的行为&#xff0c;从而在一定程度上缓解彼此对双方信用的猜疑&#xff0c;增加对网上购物…

IDEA报错处理

问题1 IDEA 新建 Maven 项目没有文件结构 pom 文件为空 将JDK换成1.8后解决。 网络说法&#xff1a;别用 java18&#xff0c;换成 java17 或者 java1.8 都可以&#xff0c;因为 java18 不是 LTS 版本&#xff0c;有着各种各样的问题。。

PowerShell实战:Get-Content命令使用详解

目录 一、Get-Content介绍 二、语法格式 三、参数详解 四、使用案例 4.1 获取文件内容 4.2 获取文件前三行内容 4.3 获取文件最后三行内容 4.4通过管道方式获取最后两行内容 4.5使用逗号作为分隔符 4.6 Filter方式读取多个文件 4.7 Include方式读取多个文件 一、Get-Content介绍…

安装android studio

记录一下安装android studio的过程&#xff1a; 1.首先安装android studio到某一文件夹后&#xff0c;在C盘用户目录下可以看到.android文件夹。C:\Users\22515\AppData\Local\Google目录下也会出现AndroidStudio2022.2文件夹。&#xff08;注意&#xff1a;用户名&#xff0c…

还在为学MyBatis发愁?史上最全,一篇文章带你学习MyBatis

文章目录 前言一、&#x1f4d6;MyBatis简介1.Mybatis历史2.MyBatis特性3.对比&#xff08;其他持久化层技术&#xff09; 二、&#x1f4e3;搭建MyBatis1.开发环境2.创建maven工程3.创建MyBatis核心配置文件4.创建mapper接口5.创建MyBatis的映射文件6.通过junit测试功能7.加入…

lambda自定义比较规则-sort函数或优先队列

Lambda表达式的一般形式为 [captures](params){body}对于优先队列的自定义排序规则&#xff0c;常见方法是写成结构体形式 struct cmp{bool operator()(pair<int,int> map1,pair<int,int> map2){return map1.second>map2.second;} }; priority_queue<pair&…

【C语言】自定义类型——枚举、联合体

引言 对枚举、联合体进行介绍&#xff0c;包括枚举的声明、枚举的优点&#xff0c;联合体的声明、联合体的大小。 ✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&#xff1a;《C语言进阶》 &#x1f388;跟着猪巴戒&#xff0c;一起学习C语言&#x1f388; 目录 引言 枚举 枚举…

利用原始套接字解决mac地址错误问题【南瑞SysKeeper-2000】

一&#xff1a;案例描述 一键可视顺控图像智能项目在网络部署过程中&#xff0c;对网络限制隔离安全性要求很高&#xff0c;用到正向隔离装置&#xff08;南瑞SysKeeper-2000型号&#xff09;。 图一 正向装置示意图 现场发现问题&#xff1a;直连网线情况下&#xff0c;我方…

排序 | 冒泡 插入 希尔 选择 堆 快排 归并 非递归 计数 基数 排序

排序 | 冒泡 插入 希尔 选择 堆 快排 归并 非递归 计数 基数 排序 文章目录 排序 | 冒泡 插入 希尔 选择 堆 快排 归并 非递归 计数 基数 排序前言&#xff1a;冒泡排序插入排序希尔排序选择排序堆排序快速排序--交换排序三数取中快速排序hoare版本快速排序挖坑法快速排序前后指…