简体   繁体   English

如何在 RecyclerView 中制作粘性标题? (有或没有外部库)

[英]How can I make sticky headers in RecyclerView? (with or without external lib)

I'm using room db and I've a table from where I get a list of LiveData.我正在使用房间数据库,并且我有一张表格,可以从中获取 LiveData 列表。 In that table there is a column of Date where I store current date.在该表中有一个 Date 列,我在其中存储当前日期。 Current date is selected by default, but user can also change the date when inserting data in the database.默认选择当前日期,但用户也可以在向数据库中插入数据时更改日期。

I want to show this data in a recyclerview in this manner我想以这种方式在回收站视图中显示这些数据图片 https://imgur.com/RYegFpG https://imgur.com/RYegFpG

I want to section this data according to the month and year as header and all entries of that month year below it.我想根据月份和年份将此数据划分为 header 以及该月份年份的所有条目。

For example, user inserted data in October 2019, I want this "October 2019" as a header in recyclerview and all entries of this month below it.例如,用户在 2019 年 10 月插入数据,我希望这个“2019 年 10 月”作为 recyclerview 中的 header 及其下方本月的所有条目。 Just like this all months entries should be shown with same manner as every next month becomes header and the entries of that month below it.就像这样,所有月份的条目都应该以相同的方式显示,因为每个下个月变成 header 以及该月下面的条目。

I've tried to achieve this by doing我试图通过这样做来实现这一目标

if (!thisDate.equals(dBDate))
        {
            holder.transMonthWrapper.setVisibility(View.VISIBLE);

            if (IEList.getType().equalsIgnoreCase("income"))
            {
                String amount = ""+IEList.getAmount();
                holder.tvTransAmount.setText(amount);
                holder.tvTransAmount.setTextColor(Color.GREEN);

                holder.tvTransCategory.setText(IEList.getCategory());
                holder.tvTransCategory.setTextColor(Color.GREEN);
            }
            else
            {
                String amount = ""+IEList.getAmount();
                holder.tvTransAmount.setText(amount);
                holder.tvTransAmount.setTextColor(Color.RED);

                holder.tvTransCategory.setText(IEList.getCategory());
                holder.tvTransCategory.setTextColor(Color.RED);
            }

            thisDate = dBDate;

            holder.tvTransMonth.setText(thisDate);
        }

        else
        {
            holder.transMonthWrapper.setVisibility(View.GONE);

            if (IEList.getType().equalsIgnoreCase("income"))
            {
                String amount = ""+IEList.getAmount();
                holder.tvTransAmount.setText(amount);
                holder.tvTransAmount.setTextColor(Color.GREEN);

                holder.tvTransCategory.setText(IEList.getCategory());
                holder.tvTransCategory.setTextColor(Color.GREEN);
            }
            else
            {
                String amount = ""+IEList.getAmount();
                holder.tvTransAmount.setText(amount);
                holder.tvTransAmount.setTextColor(Color.RED);

                holder.tvTransCategory.setText(IEList.getCategory());
                holder.tvTransCategory.setTextColor(Color.RED);
            }
        }

But the problem in this code is that when user change the month from settings and put some entries into database and that year's month entries are already present in the recyclerview.但是这段代码中的问题是,当用户从设置中更改月份并将一些条目放入数据库时,那一年的月份条目已经存在于 recyclerview 中。 It creates another header of that existing month in recyclerview.它会在 recyclerview 中创建该现有月份的另一个 header。 But I want this to put those entries in existing month header, not to create new header of that month.但我希望将这些条目放在现有月份 header 中,而不是创建该月的新 header。

What will be the best approach to achieve this without using external libraries, because I don't want to be dependent on external libraries in this case.在不使用外部库的情况下实现这一目标的最佳方法是什么,因为在这种情况下我不想依赖外部库。

I'm fairly new in programming.我在编程方面相当新。

Updated更新

In activity活动中

 public void getTransactionData()
{
    adapter = new TransactionAdapter();
    recyclerView.setAdapter(adapter);

    incomeExpenseModel = ViewModelProviders.of(AllTransaction.this).get(IncomeExpenseViewModel.class);
    incomeExpenseModel.getIncomeExpenseData().observe(this, new Observer<List<IncomeExpense>>() {
        @Override
        public void onChanged(List<IncomeExpense> incomeExpenses) {

            adapter.setIncomeExpenseList(incomeExpenses);
        }
    });

In recyclerAdapter在回收器适配器中

public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {

    IncomeExpense IEList = incomeExpenseList.get(position);

    preferences = context.getSharedPreferences(settingPref, Context.MODE_PRIVATE);
    String dateFormat = preferences.getString("Date_Format", "MM.dd.yy");

    int lastIndex = incomeExpenseList.size() - 1;
    IncomeExpense IELastIndex = incomeExpenseList.get(lastIndex);

    String dateFrmDb= IELastIndex.getDate();
    DateFormat df=new SimpleDateFormat(dateFormat);
    Date d;

    try {
        d = df.parse(dateFrmDb);
        df=new SimpleDateFormat("MMMM yyyy");
        if (d != null) {
            dBDate = df.format(d);
        }
    } catch (ParseException e) {

        Toast.makeText(context, "Error" +e, Toast.LENGTH_SHORT).show();
    }

    if (!thisDate.equals(dBDate))
    {
        holder.transMonthWrapper.setVisibility(View.VISIBLE);

        if (IEList.getType().equalsIgnoreCase("income"))
        {
            String amount = ""+IEList.getAmount();
            holder.tvTransAmount.setText(amount);
            holder.tvTransAmount.setTextColor(Color.GREEN);

            holder.tvTransCategory.setText(IEList.getCategory());
            holder.tvTransCategory.setTextColor(Color.GREEN);
        }
        else
        {
            String amount = ""+IEList.getAmount();
            holder.tvTransAmount.setText(amount);
            holder.tvTransAmount.setTextColor(Color.RED);

            holder.tvTransCategory.setText(IEList.getCategory());
            holder.tvTransCategory.setTextColor(Color.RED);
        }

        thisDate = dBDate;

        holder.tvTransMonth.setText(thisDate);
    }

    else
    {
        holder.transMonthWrapper.setVisibility(View.GONE);

        if (IEList.getType().equalsIgnoreCase("income"))
        {
            String amount = ""+IEList.getAmount();
            holder.tvTransAmount.setText(amount);
            holder.tvTransAmount.setTextColor(Color.GREEN);

            holder.tvTransCategory.setText(IEList.getCategory());
            holder.tvTransCategory.setTextColor(Color.GREEN);
        }
        else
        {
            String amount = ""+IEList.getAmount();
            holder.tvTransAmount.setText(amount);
            holder.tvTransAmount.setTextColor(Color.RED);

            holder.tvTransCategory.setText(IEList.getCategory());
            holder.tvTransCategory.setTextColor(Color.RED);
        }
    }

}

@Override
public int getItemCount() {
    return incomeExpenseList.size();
}

public void setIncomeExpenseList(List<IncomeExpense> incomeExpenseList)
{
    this.incomeExpenseList = incomeExpenseList;
    notifyDataSetChanged();
}

You don't need to use any third party libraries.您不需要使用任何第三方库。 You can make use of ExpandableListView without actually making it "expand and collapse" to do the exact same thing which you need.您可以使用ExpandableListView而无需实际使其“展开和折叠”来执行您需要的完全相同的事情。 See my answer for this post . 请参阅我对这篇文章的回答 The advantage here is that you can deal with this as easily as you deal with an ExpandableListView , with no custom code.这里的优点是您可以像处理ExpandableListView一样轻松地处理此问题,而无需自定义代码。 You only need to add one line to what is otherwise a standard ExpandableListView adapter.您只需在标准的ExpandableListView适配器中添加一行。

@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
    ...
    ((ExpandableListView) parent).expandGroup(groupPosition);
    ...
}

Your section headers, you can set as a group view and the list items as children.您可以将部分标题设置为组视图,并将列表项设置为子项。 Your data structure need to be updated before being passed on to the adapter.在传递给适配器之前,您的数据结构需要更新。 It need to be a grouped data, not a plain list which you have to pass to the expandable adapter(for example an array of classes, each instance that contain a String property for group header and an ArrayList of IncomeExpense objects).它必须是分组数据,而不是必须传递给可扩展适配器的普通列表(例如,类数组,每个实例包含组 header 的String属性和收入IncomeExpense ArrayList And when you update the data, make the update in the corresponding group, instead of the entire data.并且当您更新数据时,在相应的组中进行更新,而不是整个数据。

Parent recyclerview父回收站view

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".fragments.PhotoFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_photo"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        android:clipToPadding="false"
        android:paddingBottom="170dp">

    </androidx.recyclerview.widget.RecyclerView>

</FrameLayout>

Its adapter它的适配器

public class PhotoAdapter extends RecyclerView.Adapter<PhotoAdapter.ViewHolder> {

    private static final String TAG = "PhotoAdapter";
    private Map<String, List<Photo>> photoMap;
    private Context context;
    private PhotoCategoryAdapter photoCategoryAdapter;
    private List<String> keysList;

    public PhotoAdapter(Map<String, List<Photo>> photoMap, Context context, List<String> keysList) {
        this.photoMap = photoMap;
        this.context = context;
        this.keysList = keysList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.row_photo, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        String date  = keysList.get(position);
        holder.lblTakenDate.setText(date);

        List<Photo> photoList = photoMap.get(date);
        String size = ("( "+ photoList.size() + " )");
        holder.lblCountPhotos.setText(size);

        setRecyclerView(holder, context, photoList);

    }

    @Override
    public int getItemCount() {
        return photoMap.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.rv_photo_category) RecyclerView recyclerView;
        @BindView(R.id.lbl_taken_date_photo) TextView lblTakenDate;
        @BindView(R.id.lbl_count_images_photo) TextView lblCountPhotos;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }
}

Reyclerview inside recyclerview Recyclerview里面的Reyclerview

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp">

    <TextView
        android:id="@+id/lbl_taken_date_photo"
        android:textColor="@android:color/black"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="5dp"
        android:textSize="14dp"
        android:hint="23-Aug-2018" />

    <TextView
        android:id="@+id/lbl_count_images_photo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="5dp"
        android:layout_toRightOf="@id/lbl_taken_date_photo"
        android:hint="(2)"
        android:textSize="12sp" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_photo_category"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/lbl_taken_date_photo"
        android:layout_marginTop="10dp"
        android:background="@color/light_grey" />

</RelativeLayout>

Its adapter它的适配器

public class PhotoCategoryAdapter extends RecyclerView.Adapter<PhotoCategoryAdapter.ViewHolder> {

    private static final String TAG = "PhotoCategoryAdapter";
    private List<Photo> photoList;
    private Context context;

    public PhotoCategoryAdapter(List<Photo> photoList, Context context) {
        this.photoList = photoList;
        this.context = context;

    }

    @NonNull
    @Override
    public PhotoCategoryAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.row_category_photo, parent, false));
    }


    @Override
    public void onBindViewHolder(@NonNull PhotoCategoryAdapter.ViewHolder holder, int position) {
        Photo photo = photoList.get(position);

        RequestOptions myOptions = new RequestOptions() .format(DecodeFormat.PREFER_ARGB_8888).
                fitCenter().override(100, 100).placeholderOf(R.drawable.ic_image);

        Glide.with(context)
                .applyDefaultRequestOptions(myOptions)
                .asBitmap()
                .load(photo.getImage())
                .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
                .into(holder.imgVImages);
    }

    @Override
    public int getItemCount() {
        return photoList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.imgv_images_category_photo)
        ImageView imgVImages;
        @BindView(R.id.imgv_selected_icon_photo) ImageView imgVSelectedPhoto;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }
}

Row of inner recyclerview内部recyclerview的行

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_margin="5dp"
    android:layout_width="80dp"
    android:layout_height="80dp">

    <ImageView
        android:id="@+id/imgv_images_category_photo"
        android:scaleType="fitXY"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/imgv_selected_icon_photo"
        android:visibility="gone"
        android:layout_width="15dp"
        android:layout_height="15dp"
        android:layout_margin="10dp"
        android:src="@drawable/ic_select"
        android:layout_alignParentEnd="true"/>

</RelativeLayout>

Activity or fragment from where you will send data to adapter将数据发送到适配器的活动或片段

public class PhotoFragment extends Fragment {

    @BindView(R.id.rv_photo) RecyclerView recyclerView;

    private PhotoAdapter photoAdapter;
    private ArrayList<Photo> photoList;
    private ArrayList<String> keyList;
    private Map<String,List<Photo>> map;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_photo, container, false);
        ButterKnife.bind(this, view);

        init();
        setRecyclerViewAdapter();
        new PhotoAsync(getContext()).execute();

        return view;
    }

    private void init(){
        map = new HashMap<>();
        keyList = new ArrayList<>();
        photoList = new ArrayList<>();
    }

    //set layout manager to recyclerView
    private void setRecyclerViewAdapter() {
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
    }

    //get list of images
    @RequiresApi(api = Build.VERSION_CODES.Q)
    private List<Photo> getAllImages(){
        Uri u = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        String[] projection = {MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.DATE_TAKEN};
        Cursor c = null;
        ArrayList<Photo> photoList = new ArrayList<>();

        if (u != null) {
            c = getContext().getContentResolver().query(u, projection, null, null, null); }

        if ((c != null) && (c.moveToFirst())) {
            do {
                Photo photo = new Photo();
                String path = c.getString(c.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA));

                String takenDate = c.getString(c.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_TAKEN));
                long millisecond = Long.parseLong(takenDate);
                String date = DateFormat.format("dd-MMM-yyyy", new Date(millisecond)).toString();

                try{
                    photo.setImage(path);
                    photo.setDate(date);
                    photoList.add(photo);
                }
                catch(Exception e)
                { }

            }
            while (c.moveToNext());
        }
        //reverse photoList
        Collections.reverse(photoList);

        return photoList;
    }

    public class PhotoAsync extends AsyncTask<Void, Void,  Void> {
        private Context context;

        public PhotoAsync(Context context) {
            this.context = context;
        }

        @RequiresApi(api = Build.VERSION_CODES.Q)
        @Override
        protected Void doInBackground(Void... params) {
            getPhotoList();
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            photoAdapter = new PhotoAdapter(map, context, keyList);
            recyclerView.setAdapter(photoAdapter);
            photoAdapter.notifyDataSetChanged();
        }

        @Override
        protected void onPreExecute() {
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.Q)
    private void getPhotoList(){
        photoList.addAll(getAllImages());

        for (Photo string : photoList) {
            if (!keyList.contains(string.getDate()))
                keyList.add(string.getDate());
            else;
        }

        for (String s : keyList){
            ArrayList<Photo> photos = new ArrayList<>();
            for (Photo s1 : photoList) {
                if (s1.getDate().equals(s))
                    photos.add(s1);
                else;
            }
            map.put(s, photos);
        }
    }
}
Here is the logic which you have to apply 
1. Find all the common dates from the data list 
   for example you data list is : 
   List<Model> dataList = new ArrayList<>();
   dataList.add(new Model("23/10/19"));
   dataList.add(new Model("23/10/19"));
   dataList.add(new Model("23/09/19"));
   dataList.add(new Model("23/10/19"));
   dataList.add(new Model("27/09/19"));
   dataList.add(new Model("23/10/19"));

   List<String> commonList = new ArrayList<>();
   for(Model m : dataList){
       if(!commonList.contains(model.getDate()))
             commonList.add(m.getDate());
       else
           Log.d("Dates", commonList);
   }

   Above function will help in getting all common dates

   //Here map store date with data which has common date
   Map<String, List<Model>> map = new HashMap<>(); 
   List<Model> objectsofCommonDate = new ArrayList();

   for(String date: commonList){
       objectsofCommonDate.clear();
       for(Model model : dataList){
           if(model.getData.contains(date))
               objectsofCommonDate.add(model);
           else
             \\do nothing
       }
       map.put(data, objectsOfCommonDate);
   }


   and pass map to main recyclerview adapter along with commonDateList;
  1. First in you setIncomeExpenseList function clear your previous list.首先在您的 setIncomeExpenseList function 中清除您之前的列表。
  2. Then reAssign new list that contains full data from DB.然后重新分配包含来自数据库的完整数据的新列表。
  3. Change notifyDataSetChanged() to notify() or notifyAll().将 notifyDataSetChanged() 更改为 notify() 或 notifyAll()。

    public void setIncomeExpenseList(List<IncomeExpense> incomeExpenseList) { this.incomeExpenceList.clear(); this.incomeExpenseList = incomeExpenseList; notify(); }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM