Как я могу сделать липкие заголовки в RecyclerView? (с внешней библиотекой или без нее) - PullRequest
0 голосов
/ 01 ноября 2019

Я использую room db и у меня есть таблица, откуда я получаю список LiveData. В этой таблице есть столбец Дата, где я храню текущую дату. Текущая дата выбрана по умолчанию, но пользователь также может изменить дату при вставке данных в базу данных.

Я хочу показать эти данные в представлении переработчика следующим образом https://imgur.com/RYegFpG

Я хочу разделить эти данные в соответствии с месяцем и годом в качестве заголовка и всеми записями этого месяца и года под ним. .

Например, пользователь вставил данные в октябре 2019 года, я хочу, чтобы этот «октябрь 2019» был заголовком в обзоре реселлера, а все записи этого месяца были ниже. Точно так же записи всех месяцев должны отображаться так же, как каждый следующий месяц становится заголовком, а записи этого месяца под ним.

Я пытался добиться этого, выполняя

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);
            }
        }

Но проблема в этом коде заключается в том, что когда пользователь меняет месяц с настроек и помещает некоторые записи в базу данных и записи месяца того годауже присутствуют в обзоре переработчика. Это создает еще один заголовок этого существующего месяца в переработке. Но я хочу, чтобы это помещало эти записи в существующий заголовок месяца, а не создавало новый заголовок этого месяца.

Каков наилучший подход для достижения этого без использования внешних библиотек, потому что я не хочу бытьзависит от внешних библиотек в этом случае.

Я довольно новичок в программировании.

Обновлено

В действии

 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);
        }
    });

В программе утилизации Адаптер

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();
}

Ответы [ 3 ]

1 голос
/ 01 ноября 2019

Родительский просмотрщик утилит

<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>

Его адаптер

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);
        }
    }
}

Обзор Reycler внутри утилитного просмотра

<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>

Его адаптер

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);
        }
    }
}

Рядвнутренний просмотрщик

<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>

Активность или фрагмент, из которого вы будете отправлять данные в адаптер

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 голос
/ 01 ноября 2019
  1. Первая в вас функция setIncomeExpenseList очистит ваш предыдущий список.
  2. Затем переназначьте новый список, содержащий полные данные из БД.
  3. Измените notifyDataSetChanged () на notify () или notifyAll ().

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

1 голос
/ 01 ноября 2019

Вам не нужно использовать какие-либо сторонние библиотеки. Вы можете использовать ExpandableListView, фактически не заставляя его «разворачиваться и разворачиваться», чтобы делать то же самое, что вам нужно. Смотрите мой ответ на этот пост . Преимущество здесь в том, что вы можете справиться с этим так же легко, как и с ExpandableListView, без специального кода. Вам нужно добавить только одну строку к тому, что в противном случае является стандартным ExpandableListView адаптером.

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

Ваши заголовки разделов можно установить как групповое представление, а элементы списка - как дочерние. Ваша структура данных должна быть обновлена ​​перед передачей на адаптер. Это должны быть сгруппированные данные, а не простой список, который необходимо передать расширяемому адаптеру (например, массив классов, каждый экземпляр которого содержит свойство String для заголовка группы и ArrayList из IncomeExpenseобъекты). И когда вы обновляете данные, производите обновление в соответствующей группе вместо полных данных.

...