আমি কীভাবে রিসাইক্লারভিউতে স্টিকি হেডার তৈরি করতে পারি? (বহিরাগত bতিহ্য ছাড়া)


120

আমি আমার হেডার দর্শনগুলি নীচের চিত্রের মতো এবং বাহ্যিক লাইব্রেরি ব্যবহার না করে পর্দার শীর্ষে স্থির করতে চাই to

এখানে চিত্র বর্ণনা লিখুন

আমার ক্ষেত্রে, আমি এটি বর্ণানুক্রমিকভাবে করতে চাই না। আমার দুটি ভিন্ন ধরণের ভিউ রয়েছে (শিরোনাম এবং স্বাভাবিক)। আমি কেবল শেষ শিরোনামে শীর্ষে ফিক্স করতে চাই।


17
প্রশ্নটি রিসাইক্লার ভিউ সম্পর্কে ছিল, এই ^ লিবটি তালিকাভিউ
ম্যাক্স সিএইচ

উত্তর:


319

এখানে আমি বাহ্যিক গ্রন্থাগার ছাড়া এটি কীভাবে করব তা ব্যাখ্যা করব। এটি একটি দীর্ঘ পোস্ট হবে, তাই নিজেকে বন্ধনী।

সবার আগে, আমি @ টিম.পেটজকে স্বীকৃতি দিই যার পোস্ট আমাকে ItemDecorationএস ব্যবহার করে আমার নিজের স্টিকি হেডারগুলি বাস্তবায়নের পথে যাত্রা করতে অনুপ্রাণিত করেছিল । আমি আমার প্রয়োগে তার কোডের কিছু অংশ ধার নিয়েছি।

আপনি ইতিমধ্যে অভিজ্ঞ হয়ে থাকতে পারেন, আপনি যদি নিজে এটি করার চেষ্টা করে থাকেন তবে কৌশলটি দিয়ে বাস্তবে এটি করার জন্য HOW এর একটি ভাল ব্যাখ্যা পাওয়া খুব কঠিন ItemDecoration। মানে, পদক্ষেপগুলি কী? এর পিছনে যুক্তি কী? আমি কীভাবে তালিকার শীর্ষে হেডারটিকে স্টিক করব? এই প্রশ্নের উত্তর না জেনে হ'ল অন্যরা বাহ্যিক গ্রন্থাগারগুলি ব্যবহার করতে বাধ্য করে, যখন এটির ব্যবহারের সাথে নিজেই এটি ItemDecorationকরা খুব সহজ।

প্রাথমিক শর্তাবলি

  1. আপনার ডেটাসেটটি listবিভিন্ন ধরণের আইটেমের হওয়া উচিত ("জাভা ধরণের" অর্থে নয়, তবে "শিরোনাম / আইটেম" ধরণের অর্থে)।
  2. আপনার তালিকাটি ইতিমধ্যে বাছাই করা উচিত।
  3. তালিকার প্রতিটি আইটেম নির্দিষ্ট ধরণের হওয়া উচিত - এর সাথে সম্পর্কিত একটি শিরোনাম আইটেম থাকতে হবে।
  4. খুব প্রথম আইটেমটি listঅবশ্যই একটি শিরোনামের আইটেম হতে হবে।

এখানে আমি আমার জন্য পূর্ণ কোড প্রদান RecyclerView.ItemDecorationনামক HeaderItemDecoration। তারপরে আমি নেওয়া পদক্ষেপগুলি বিস্তারিতভাবে বর্ণনা করি।

public class HeaderItemDecoration extends RecyclerView.ItemDecoration {

 private StickyHeaderInterface mListener;
 private int mStickyHeaderHeight;

 public HeaderItemDecoration(RecyclerView recyclerView, @NonNull StickyHeaderInterface listener) {
  mListener = listener;

  // On Sticky Header Click
  recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
   public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {
    if (motionEvent.getY() <= mStickyHeaderHeight) {
     // Handle the clicks on the header here ...
     return true;
    }
    return false;
   }

   public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {

   }

   public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

   }
  });
 }

 @Override
 public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
  super.onDrawOver(c, parent, state);

  View topChild = parent.getChildAt(0);
  if (Util.isNull(topChild)) {
   return;
  }

  int topChildPosition = parent.getChildAdapterPosition(topChild);
  if (topChildPosition == RecyclerView.NO_POSITION) {
   return;
  }

  View currentHeader = getHeaderViewForItem(topChildPosition, parent);
  fixLayoutSize(parent, currentHeader);
  int contactPoint = currentHeader.getBottom();
  View childInContact = getChildInContact(parent, contactPoint);
  if (Util.isNull(childInContact)) {
   return;
  }

  if (mListener.isHeader(parent.getChildAdapterPosition(childInContact))) {
   moveHeader(c, currentHeader, childInContact);
   return;
  }

  drawHeader(c, currentHeader);
 }

 private View getHeaderViewForItem(int itemPosition, RecyclerView parent) {
  int headerPosition = mListener.getHeaderPositionForItem(itemPosition);
  int layoutResId = mListener.getHeaderLayout(headerPosition);
  View header = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, false);
  mListener.bindHeaderData(header, headerPosition);
  return header;
 }

 private void drawHeader(Canvas c, View header) {
  c.save();
  c.translate(0, 0);
  header.draw(c);
  c.restore();
 }

 private void moveHeader(Canvas c, View currentHeader, View nextHeader) {
  c.save();
  c.translate(0, nextHeader.getTop() - currentHeader.getHeight());
  currentHeader.draw(c);
  c.restore();
 }

 private View getChildInContact(RecyclerView parent, int contactPoint) {
  View childInContact = null;
  for (int i = 0; i < parent.getChildCount(); i++) {
   View child = parent.getChildAt(i);
   if (child.getBottom() > contactPoint) {
    if (child.getTop() <= contactPoint) {
     // This child overlaps the contactPoint
     childInContact = child;
     break;
    }
   }
  }
  return childInContact;
 }

 /**
  * Properly measures and layouts the top sticky header.
  * @param parent ViewGroup: RecyclerView in this case.
  */
 private void fixLayoutSize(ViewGroup parent, View view) {

  // Specs for parent (RecyclerView)
  int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
  int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);

  // Specs for children (headers)
  int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width);
  int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), view.getLayoutParams().height);

  view.measure(childWidthSpec, childHeightSpec);

  view.layout(0, 0, view.getMeasuredWidth(), mStickyHeaderHeight = view.getMeasuredHeight());
 }

 public interface StickyHeaderInterface {

  /**
   * This method gets called by {@link HeaderItemDecoration} to fetch the position of the header item in the adapter
   * that is used for (represents) item at specified position.
   * @param itemPosition int. Adapter's position of the item for which to do the search of the position of the header item.
   * @return int. Position of the header item in the adapter.
   */
  int getHeaderPositionForItem(int itemPosition);

  /**
   * This method gets called by {@link HeaderItemDecoration} to get layout resource id for the header item at specified adapter's position.
   * @param headerPosition int. Position of the header item in the adapter.
   * @return int. Layout resource id.
   */
  int getHeaderLayout(int headerPosition);

  /**
   * This method gets called by {@link HeaderItemDecoration} to setup the header View.
   * @param header View. Header to set the data on.
   * @param headerPosition int. Position of the header item in the adapter.
   */
  void bindHeaderData(View header, int headerPosition);

  /**
   * This method gets called by {@link HeaderItemDecoration} to verify whether the item represents a header.
   * @param itemPosition int.
   * @return true, if item at the specified adapter's position represents a header.
   */
  boolean isHeader(int itemPosition);
 }
}

ব্যবসায়িক যুক্তি

তো, আমি কীভাবে এটি স্টিক করব?

আপনি না। আপনি RecyclerViewনিজের পছন্দের কোনও আইটেমটি কেবল থামাতে এবং উপরে আটকে রাখতে পারবেন না , যদি না আপনি কাস্টম লেআউটের গুরু হন এবং আপনি RecyclerViewহৃদয় দিয়ে 12,000+ কোডের লাইন জানেন না । সুতরাং, যেমন এটি সর্বদা ইউআই ডিজাইনের সাথে চলে আপনি যদি কিছু তৈরি করতে না পারেন তবে এটি জাল করুন। আপনি ব্যবহার করে সমস্ত কিছুর শীর্ষে শিরোনাম আঁকুনCanvas । এই মুহুর্তে ব্যবহারকারী কোন আইটেম দেখতে পাবে তাও আপনার জানা উচিত। এটি কেবল ঘটে যায়, যা ItemDecorationআপনাকে Canvasদৃশ্যমান আইটেমগুলি এবং উভয়ই তথ্য সরবরাহ করতে পারে । এটির সাথে, এখানে প্রাথমিক পদক্ষেপগুলি রয়েছে:

  1. ইন onDrawOverপদ্ধতি RecyclerView.ItemDecorationপ্রথম (শীর্ষ) আইটেমটি ব্যবহারকারীকে কাছে দৃশ্যমান পেতে।

        View topChild = parent.getChildAt(0);
  2. কোন শিরোলেখ এটি উপস্থাপন করে তা নির্ধারণ করুন।

            int topChildPosition = parent.getChildAdapterPosition(topChild);
        View currentHeader = getHeaderViewForItem(topChildPosition, parent);
    
  3. drawHeader()পদ্ধতিটি ব্যবহার করে পুনর্ব্যবহারযোগ্যের শীর্ষে যথাযথ শিরোনাম আঁকুন ।

নতুন আসন্ন শিরোনাম যখন শীর্ষের সাথে মিলিত হয় তখন আমি আচরণটিও বাস্তবায়িত করতে চাই: আসন্ন শিরোনামটি আস্তে আস্তে শীর্ষের বর্তমান শিরোনামটিকে দৃষ্টিতে বাইরে ঠেলে দেয় এবং অবশেষে তার স্থান নেয় takes

"সমস্ত কিছুর উপরে অঙ্কন" করার একই কৌশল এখানে প্রয়োগ হয়।

  1. শীর্ষ "আটকে থাকা" শিরোনামটি নতুন আসন্নটির সাথে মিলবে কিনা তা নির্ধারণ করুন।

            View childInContact = getChildInContact(parent, contactPoint);
  2. এই যোগাযোগের বিন্দুটি পান (এটি আপনার স্ট্রিচের শীর্ষের নীচের অংশ এবং আসন্ন শিরোনামের শীর্ষে)।

            int contactPoint = currentHeader.getBottom();
  3. তালিকার আইটেমটি যদি এই "যোগাযোগের বিন্দু "টিকে ত্রুটিযুক্ত করে তোলে তবে আপনার স্টিকি হেডারটি আবার আঁকুন যাতে এর নীচের অংশটি দোষী আইটেমের শীর্ষে থাকবে। আপনি translate()পদ্ধতিতে এটি অর্জন Canvas। ফলস্বরূপ, শীর্ষ শিরোলেখার শুরুর পয়েন্টটি দৃশ্যমান ক্ষেত্রের বাইরে চলে যাবে এবং এটি "আসন্ন শিরোনাম দ্বারা ধাক্কা মেরে ফেলেছে" বলে মনে হবে। এটি সম্পূর্ণরূপে শেষ হয়ে গেলে, শীর্ষে নতুন শিরোনামটি আঁকুন।

            if (childInContact != null) {
            if (mListener.isHeader(parent.getChildAdapterPosition(childInContact))) {
                moveHeader(c, currentHeader, childInContact);
            } else {
                drawHeader(c, currentHeader);
            }
        }
    

বাকিটি আমার দেওয়া কোডের টুকরোটিতে মন্তব্য এবং পুঙ্খানুপুঙ্খ টিকা দ্বারা ব্যাখ্যা করা হয়েছে।

ব্যবহারটি সরাসরি এগিয়ে রয়েছে:

mRecyclerView.addItemDecoration(new HeaderItemDecoration((HeaderItemDecoration.StickyHeaderInterface) mAdapter));

এটি কাজ করার জন্য আপনার mAdapterঅবশ্যই প্রয়োগ StickyHeaderInterfaceকরতে হবে। বাস্তবায়ন আপনার থাকা ডেটার উপর নির্ভর করে।

অবশেষে, আমি এখানে অর্ধ-স্বচ্ছ শিরোনাম সহ একটি জিআইএফ সরবরাহ করি, যাতে আপনি ধারণাটি উপলব্ধি করতে পারেন এবং প্রকৃতপক্ষে হুডের নীচে কী চলছে তা দেখতে পারেন।

এখানে "সমস্ত কিছুর উপরে কেবল আঁকুন" ধারণাটির চিত্রণ দেওয়া আছে। আপনি দেখতে পাচ্ছেন যে দুটি শিরোনাম রয়েছে "শিরোনাম 1" - একটি যা আমরা আঁকতে পারি এবং আটকে থাকা অবস্থায় শীর্ষে থাকি এবং অন্যটি যা ডেটাসেট থেকে আসে এবং বাকী সমস্ত আইটেম নিয়ে চলে যায়। ব্যবহারকারী এর অভ্যন্তরীণ কাজগুলি দেখতে পাবে না, কারণ আপনার অর্ধেক স্বচ্ছ শিরোনাম পাবেন না।

"কেবলমাত্র সবার উপরে আঁকুন" ধারণাটি

এবং এখানে "পুশিং আউট" পর্বে কী ঘটে:

"পুশ আউট" পর্ব

আশা করি এটি সাহায্য করেছে।

সম্পাদন করা

আমার getHeaderPositionForItem()পুনর্ব্যবহারযোগ্য ভিউয়ের অ্যাডাপ্টারে পদ্ধতিটির আসল বাস্তবায়ন এখানে রয়েছে :

@Override
public int getHeaderPositionForItem(int itemPosition) {
    int headerPosition = 0;
    do {
        if (this.isHeader(itemPosition)) {
            headerPosition = itemPosition;
            break;
        }
        itemPosition -= 1;
    } while (itemPosition >= 0);
    return headerPosition;
}

কোটলিনে কিছুটা ভিন্ন বাস্তবায়ন


4
@ সেবাস্তান কেবল উজ্জ্বল! আপনি যেভাবে এই চ্যালেঞ্জটি সমাধান করেছেন আমি সত্যিই পছন্দ করেছি। বলার অপেক্ষা রাখে না, সম্ভবত একটি প্রশ্ন ব্যতীত: "চটচটে শিরোলেখ "টিতে একটি অনক্লিকলিস্টার সেট করার কোনও উপায় আছে, বা কমপক্ষে ক্লিকটি ব্যবহারকারীকে এর মাধ্যমে ক্লিক করা আটকাতে হবে?
ডেনিস

17
আপনি যদি এই প্রয়োগের অ্যাডাপ্টারের উদাহরণটি রাখেন তবে দুর্দান্ত হবে
সলিডস্পেক

1
আমি অবশেষে এটি এখানে এবং সেখানে কয়েকটি টুইটের সাথে কাজ করতে তৈরি করেছি। যদিও আপনি যদি আপনার আইটেমগুলিতে কোনও প্যাডিং যুক্ত করেন এটি যখনই প্যাডেড অঞ্চলে স্ক্রোল করবেন তখন এটি ঝাঁকুনি দেওয়া থাকবে। আপনার আইটেমের বিন্যাসে সমাধান 0 প্যাডিং সহ একটি প্যারেন্ট লেআউট এবং আপনার পছন্দসই প্যাডিংয়ের সাথে একটি শিশু বিন্যাস তৈরি করুন।
সলডস্পেক

8
ধন্যবাদ। আকর্ষণীয় সমাধান, তবে প্রতিটি স্ক্রোল ইভেন্টে শিরোলেখের দর্শনটি বাড়ানো একটু ব্যয়বহুল। আমি কেবল যুক্তিটি পরিবর্তন করেছি এবং ভিউহোল্ডার ব্যবহার করেছি এবং ইতিমধ্যে স্ফীত দৃষ্টিভঙ্গি পুনরায় ব্যবহার করতে তাদের WeakReferences এর একটি হ্যাশম্যাপে রেখেছি।
মাইকেল

4
@ শেভস্তান, দুর্দান্ত কাজ। আমার একটি পরামর্শ আছে. প্রতিবার নতুন শিরোনাম তৈরি এড়াতে। কেবল শিরোনামটি সংরক্ষণ করুন এবং এটি পরিবর্তিত হলেই এটি পরিবর্তন করুন। private View getHeaderViewForItem(int itemPosition, RecyclerView parent) { int headerPosition = mListener.getHeaderPositionForItem(itemPosition); if(headerPosition != mCurrentHeaderIndex) { mCurrentHeader = mListener.createHeaderView(headerPosition, parent); mCurrentHeaderIndex = headerPosition; } return mCurrentHeader; }
ভেরা রিভোতি

27

সহজ উপায় হ'ল আপনার রিসাইক্লার ভিউয়ের জন্য কেবল একটি আইটেম সজ্জা তৈরি করা।

import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class RecyclerSectionItemDecoration extends RecyclerView.ItemDecoration {

private final int             headerOffset;
private final boolean         sticky;
private final SectionCallback sectionCallback;

private View     headerView;
private TextView header;

public RecyclerSectionItemDecoration(int headerHeight, boolean sticky, @NonNull SectionCallback sectionCallback) {
    headerOffset = headerHeight;
    this.sticky = sticky;
    this.sectionCallback = sectionCallback;
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);

    int pos = parent.getChildAdapterPosition(view);
    if (sectionCallback.isSection(pos)) {
        outRect.top = headerOffset;
    }
}

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c,
                     parent,
                     state);

    if (headerView == null) {
        headerView = inflateHeaderView(parent);
        header = (TextView) headerView.findViewById(R.id.list_item_section_text);
        fixLayoutSize(headerView,
                      parent);
    }

    CharSequence previousHeader = "";
    for (int i = 0; i < parent.getChildCount(); i++) {
        View child = parent.getChildAt(i);
        final int position = parent.getChildAdapterPosition(child);

        CharSequence title = sectionCallback.getSectionHeader(position);
        header.setText(title);
        if (!previousHeader.equals(title) || sectionCallback.isSection(position)) {
            drawHeader(c,
                       child,
                       headerView);
            previousHeader = title;
        }
    }
}

private void drawHeader(Canvas c, View child, View headerView) {
    c.save();
    if (sticky) {
        c.translate(0,
                    Math.max(0,
                             child.getTop() - headerView.getHeight()));
    } else {
        c.translate(0,
                    child.getTop() - headerView.getHeight());
    }
    headerView.draw(c);
    c.restore();
}

private View inflateHeaderView(RecyclerView parent) {
    return LayoutInflater.from(parent.getContext())
                         .inflate(R.layout.recycler_section_header,
                                  parent,
                                  false);
}

/**
 * Measures the header view to make sure its size is greater than 0 and will be drawn
 * https://yoda.entelect.co.za/view/9627/how-to-android-recyclerview-item-decorations
 */
private void fixLayoutSize(View view, ViewGroup parent) {
    int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(),
                                                     View.MeasureSpec.EXACTLY);
    int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(),
                                                      View.MeasureSpec.UNSPECIFIED);

    int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
                                                   parent.getPaddingLeft() + parent.getPaddingRight(),
                                                   view.getLayoutParams().width);
    int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
                                                    parent.getPaddingTop() + parent.getPaddingBottom(),
                                                    view.getLayoutParams().height);

    view.measure(childWidth,
                 childHeight);

    view.layout(0,
                0,
                view.getMeasuredWidth(),
                view.getMeasuredHeight());
}

public interface SectionCallback {

    boolean isSection(int position);

    CharSequence getSectionHeader(int position);
}

}

আপনার হেডারটির জন্য রিসাইক্লার_সেকশন_হেডার.এক্সএমএল এর এক্সএমএল:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list_item_section_text"
    android:layout_width="match_parent"
    android:layout_height="@dimen/recycler_section_header_height"
    android:background="@android:color/black"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:textColor="@android:color/white"
    android:textSize="14sp"
/>

এবং অবশেষে আপনার পুনর্ব্যবহারযোগ্য ভিউতে আইটেম সজ্জা যুক্ত করতে:

RecyclerSectionItemDecoration sectionItemDecoration =
        new RecyclerSectionItemDecoration(getResources().getDimensionPixelSize(R.dimen.recycler_section_header_height),
                                          true, // true for sticky, false for not
                                          new RecyclerSectionItemDecoration.SectionCallback() {
                                              @Override
                                              public boolean isSection(int position) {
                                                  return position == 0
                                                      || people.get(position)
                                                               .getLastName()
                                                               .charAt(0) != people.get(position - 1)
                                                                                   .getLastName()
                                                                                   .charAt(0);
                                              }

                                              @Override
                                              public CharSequence getSectionHeader(int position) {
                                                  return people.get(position)
                                                               .getLastName()
                                                               .subSequence(0,
                                                                            1);
                                              }
                                          });
    recyclerView.addItemDecoration(sectionItemDecoration);

এই আইটেম সজ্জা দ্বারা আপনি হয় শিরোনাম পিন / স্টিকি করতে পারবেন বা আইটেম সজ্জা তৈরি করার সময় কেবল একটি বুলিয়ান দিয়ে নয়।

আপনি গিথুব এ সম্পূর্ণ কাজের উদাহরণ খুঁজে পেতে পারেন: https://github.com/paetztm/recycler_view_headers


ধন্যবাদ. এটি আমার পক্ষে কাজ করেছে, তবে এই শিরোনামটি পুনর্ব্যবহারযোগ্য ওভারল্যাপ করেছে। তুমি কি সাহায্য করতে পারো?
কাশ্যপ জিমুলিয়া

আমি নিশ্চিত নই আপনি রিসাইক্লার ভিউকে ওভারল্যাপ করে কী বোঝাতে চেয়েছেন। "স্টিকি" বুলিয়ানটির জন্য, যদি আপনি এটি মিথ্যা হিসাবে সেট করেন তবে এটি আইটেমের সজ্জাটিকে সারিগুলির মধ্যে রাখবে এবং পুনর্ব্যবহারযোগ্য দৃশ্যের শীর্ষে থাকবে না।
টিম.প্যাটজ

এটিকে "স্টিকি" থেকে মিথ্যাতে সেট করে সারিগুলির মধ্যে শিরোনাম রাখে, তবে এটি শীর্ষে আটকে থাকে না (যা আমি চাই না)। সত্য হিসাবে সেট করার সময়, এটি শীর্ষে আটকে থাকে তবে এটি পুনর্ব্যবহারযোগ্য
দৃশ্যে

আমি দেখতে পাচ্ছি যে সম্ভাব্য দুটি সমস্যা, একটি হচ্ছে বিভাগ কলব্যাক, আপনি প্রথম আইটেমটি (0 অবস্থান) সেট করছেন না ইসস্যাকশন সত্যের জন্য। অন্যটি আপনি ভুল উচ্চতায় চলে যাচ্ছেন। পাঠ্য দর্শনের জন্য এক্সএমএলটির উচ্চতা অবশ্যই বিভাগ আইটেমের সজ্জাতে আপনি যে উচ্চতাটি প্রেরণ করবেন তার সমান উচ্চতা হতে হবে।
টিম.প্যাটজ

3
একটি জিনিস আমি যুক্ত করব, তা হ'ল যদি আপনার শিরোনাম লেআউটে শিরোনাম পাঠ্য ভিউটি গতিশীল আকারের হয় (যেমন wrap_content), আপনি fixLayoutSizeশিরোনামের পাঠ্যটিও সেট করার পরে চালাতে চান want
কোপোলি

6

আমি উপরের পরিষেবাস্তানের সমাধানের নিজস্ব বৈকল্পিক করেছি

class HeaderItemDecoration(recyclerView: RecyclerView, private val listener: StickyHeaderInterface) : RecyclerView.ItemDecoration() {

private val headerContainer = FrameLayout(recyclerView.context)
private var stickyHeaderHeight: Int = 0
private var currentHeader: View? = null
private var currentHeaderPosition = 0

init {
    val layout = RelativeLayout(recyclerView.context)
    val params = recyclerView.layoutParams
    val parent = recyclerView.parent as ViewGroup
    val index = parent.indexOfChild(recyclerView)
    parent.addView(layout, index, params)
    parent.removeView(recyclerView)
    layout.addView(recyclerView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    layout.addView(headerContainer, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
}

override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
    super.onDrawOver(c, parent, state)

    val topChild = parent.getChildAt(0) ?: return

    val topChildPosition = parent.getChildAdapterPosition(topChild)
    if (topChildPosition == RecyclerView.NO_POSITION) {
        return
    }

    val currentHeader = getHeaderViewForItem(topChildPosition, parent)
    fixLayoutSize(parent, currentHeader)
    val contactPoint = currentHeader.bottom
    val childInContact = getChildInContact(parent, contactPoint) ?: return

    val nextPosition = parent.getChildAdapterPosition(childInContact)
    if (listener.isHeader(nextPosition)) {
        moveHeader(currentHeader, childInContact, topChildPosition, nextPosition)
        return
    }

    drawHeader(currentHeader, topChildPosition)
}

private fun getHeaderViewForItem(itemPosition: Int, parent: RecyclerView): View {
    val headerPosition = listener.getHeaderPositionForItem(itemPosition)
    val layoutResId = listener.getHeaderLayout(headerPosition)
    val header = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
    listener.bindHeaderData(header, headerPosition)
    return header
}

private fun drawHeader(header: View, position: Int) {
    headerContainer.layoutParams.height = stickyHeaderHeight
    setCurrentHeader(header, position)
}

private fun moveHeader(currentHead: View, nextHead: View, currentPos: Int, nextPos: Int) {
    val marginTop = nextHead.top - currentHead.height
    if (currentHeaderPosition == nextPos && currentPos != nextPos) setCurrentHeader(currentHead, currentPos)

    val params = currentHeader?.layoutParams as? MarginLayoutParams ?: return
    params.setMargins(0, marginTop, 0, 0)
    currentHeader?.layoutParams = params

    headerContainer.layoutParams.height = stickyHeaderHeight + marginTop
}

private fun setCurrentHeader(header: View, position: Int) {
    currentHeader = header
    currentHeaderPosition = position
    headerContainer.removeAllViews()
    headerContainer.addView(currentHeader)
}

private fun getChildInContact(parent: RecyclerView, contactPoint: Int): View? =
        (0 until parent.childCount)
            .map { parent.getChildAt(it) }
            .firstOrNull { it.bottom > contactPoint && it.top <= contactPoint }

private fun fixLayoutSize(parent: ViewGroup, view: View) {

    val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
    val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)

    val childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
            parent.paddingLeft + parent.paddingRight,
            view.layoutParams.width)
    val childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
            parent.paddingTop + parent.paddingBottom,
            view.layoutParams.height)

    view.measure(childWidthSpec, childHeightSpec)

    stickyHeaderHeight = view.measuredHeight
    view.layout(0, 0, view.measuredWidth, stickyHeaderHeight)
}

interface StickyHeaderInterface {

    fun getHeaderPositionForItem(itemPosition: Int): Int

    fun getHeaderLayout(headerPosition: Int): Int

    fun bindHeaderData(header: View, headerPosition: Int)

    fun isHeader(itemPosition: Int): Boolean
}
}

... এবং এখানে স্টিকিহাইডারআইনটারফেস বাস্তবায়ন করা হয়েছে (আমি এটি সরাসরি পুনর্ব্যবহারকারী অ্যাডাপ্টারে করেছিলাম):

override fun getHeaderPositionForItem(itemPosition: Int): Int =
    (itemPosition downTo 0)
        .map { Pair(isHeader(it), it) }
        .firstOrNull { it.first }?.second ?: RecyclerView.NO_POSITION

override fun getHeaderLayout(headerPosition: Int): Int {
    /* ... 
      return something like R.layout.view_header
      or add conditions if you have different headers on different positions
    ... */
}

override fun bindHeaderData(header: View, headerPosition: Int) {
    if (headerPosition == RecyclerView.NO_POSITION) header.layoutParams.height = 0
    else /* ...
      here you get your header and can change some data on it
    ... */
}

override fun isHeader(itemPosition: Int): Boolean {
    /* ...
      here have to be condition for checking - is item on this position header
    ... */
}

সুতরাং, এই ক্ষেত্রে শিরোনামটি কেবল ক্যানভাসে অঙ্কিত হচ্ছে না, তবে নির্বাচক বা রিপল, ক্লিকলিস্টার ইত্যাদির সাথে দেখুন


ভাগ করে নেওয়ার জন্য ধন্যবাদ! আপনি কেন নতুন রিলেটিভলয়েটে রিসাইক্লার ভিউ মোড়ানো শেষ করলেন?
tmm1

কারণ আমার স্টিকি শিরোলেখের সংস্করণটি ভিউ, যা আমি এই রিলেটিভ লেআউটের উপরে পুনর্ব্যবহারযোগ্য ভিউয়ের উপরে রেখেছি (শিরোনামকন্টেইনার ক্ষেত্রে)
আন্দ্রে তুরস্কভস্কি

আপনি কি ক্লাস ফাইলে আপনার প্রয়োগ দেখিয়ে দিতে পারেন? অ্যাডাপ্টারে প্রয়োগ করা শ্রোতার অবজেক্টটি আপনি কীভাবে পাস করেছেন।
দিপালি শাহ

recyclerView.addItemDecoration(HeaderItemDecoration(recyclerView, adapter))। দুঃখিত, বাস্তবায়নের উদাহরণ খুঁজে পাচ্ছি না, যা আমি ব্যবহার করেছি। আমি উত্তর সম্পাদনা করেছি - মন্তব্যে কিছু পাঠ্য যুক্ত করেছি
আন্দ্রে টার্কভস্কি

6

আপনার যখনই ইতিমধ্যে ঝলকানি / ঝলকানো সমস্যার সমাধান খুঁজছেন তাদের কাছে DividerItemDecoration। আমি এটি এর মতো সমাধান করেছি বলে মনে হচ্ছে:

override fun onDrawOver(...)
    {
        //code from before

       //do NOT return on null
        val childInContact = getChildInContact(recyclerView, currentHeader.bottom)
        //add null check
        if (childInContact != null && mHeaderListener.isHeader(recyclerView.getChildAdapterPosition(childInContact)))
        {
            moveHeader(...)
            return
        }
    drawHeader(...)
}

এটি কাজ করছে বলে মনে হচ্ছে তবে কেউ কি নিশ্চিত হতে পারে যে আমি অন্য কিছু ভাঙ্গেনি?


আপনাকে ধন্যবাদ, এটি আমার জন্যও জ্বলজ্বলে সমস্যা সমাধান করেছে।
ইয়ামাসিরো রিওন

3

আপনি StickyHeaderHelperআমার ফ্লেক্সিবলএডাপ্টার প্রকল্পে ক্লাসের বাস্তবায়ন পরীক্ষা করে নিতে এবং এটিকে আপনার ব্যবহারের ক্ষেত্রে অভিযোজিত করতে পারেন।

তবে, আমি লাইব্রেরিটি ব্যবহার করার পরামর্শ দিচ্ছি যেহেতু আপনি সাধারণত রিসাইক্লারভিউয়ের জন্য অ্যাডাপ্টারগুলি প্রয়োগ করেন সেই পদ্ধতিটিকে সহজতর করে এবং পুনর্গঠন করে: চাকাটিকে পুনরায় উদ্ভাবন করবেন না।

আমি আরও বলব, ডেকোরেটর বা অবহেলিত গ্রন্থাগারগুলি ব্যবহার করবেন না, পাশাপাশি কেবল 1 বা 3 টি কাজ করে এমন লাইব্রেরি ব্যবহার করবেন না, আপনাকে অন্য গ্রন্থাগারগুলির প্রয়োগগুলি নিজেই মার্জ করতে হবে।


আমি উইকি এবং নমুনা পড়তে 2 দিন অতিবাহিত করেছি, তবে এখনও আপনার লিবিব ব্যবহার করে সংযোগযোগ্য তালিকা তৈরি করতে জানেন না। নমুনা নবাগতদের জন্য বেশ জটিল
Nguyen Minh Binh

1
আপনি Decoratorএস ব্যবহারের বিরুদ্ধে কেন ?
সেবাদাস্তন সাবন্যুক

1
@ সিভাস্তান, কারণ আমরা সেখানে পৌঁছে যাব আমাদের এটির ও শিশু দৃষ্টিভঙ্গিগুলিতে ক্লিক শ্রোতা দরকার। আমরা সংজ্ঞা আপনি কেবল সংজ্ঞা দ্বারা পারবেন না।
ডেভিডাস

@ ডেভিডা, আপনার অর্থ কি আপনি ভবিষ্যতে শিরোনামে ক্লিক শ্রোতাদের সেট করতে চান? যদি তা হয় তবে তা বোধগম্য হয়। তবুও, আপনি যদি ডেটাসেট আইটেম হিসাবে আপনার শিরোনাম সরবরাহ করেন তবে কোনও সমস্যা হবে না। এমনকি ইগিত বায়ার সাজসজ্জার ব্যবহার করার পরামর্শ দেয়।
সেবাদাস্তন সাবন্যুক

@ শেভস্তান, হ্যাঁ আমার লাইব্রেরিতে শিরোনাম হ'ল তালিকার অন্যান্য হিসাবে একটি আইটেম, যাতে ব্যবহারকারীরা এটি ব্যবহার করতে পারেন। সুদূর ভবিষ্যতে একটি কাস্টম লেআউট ম্যানেজার বর্তমান সহায়ককে প্রতিস্থাপন করবে।
ডেভিডাস

3

অন্য সমাধান, স্ক্রোল শ্রোতার উপর ভিত্তি করে। প্রাথমিক শর্তগুলি সেবাদাস্তান উত্তরের মতোই

RecyclerView recyclerView;
TextView tvTitle; //sticky header view

//... onCreate, initialize, etc...

public void bindList(List<Item> items) { //All data in adapter. Item - just interface for different item types
    adapter = new YourAdapter(items);
    recyclerView.setAdapter(adapter);
    StickyHeaderViewManager<HeaderItem> stickyHeaderViewManager = new StickyHeaderViewManager<>(
            tvTitle,
            recyclerView,
            HeaderItem.class, //HeaderItem - subclass of Item, used to detect headers in list
            data -> { // bind function for sticky header view
                tvTitle.setText(data.getTitle());
            });
    stickyHeaderViewManager.attach(items);
}

ভিউহোল্ডার এবং স্টিকি হেডার জন্য লেআউট।

item_header.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

রিসাইক্লার ভিউয়ের জন্য লেআউট

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <!--it can be any view, but order important, draw over recyclerView-->
    <include
        layout="@layout/item_header"/>

</FrameLayout>

শিরোনামের জন্য ক্লাস।

public class HeaderItem implements Item {

    private String title;

    public HeaderItem(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

}

এটা সব ব্যবহার। অ্যাডাপ্টার, ভিউহোল্ডার এবং অন্যান্য জিনিসগুলির বাস্তবায়ন আমাদের পক্ষে আকর্ষণীয় নয়।

public class StickyHeaderViewManager<T> {

    @Nonnull
    private View headerView;

    @Nonnull
    private RecyclerView recyclerView;

    @Nonnull
    private StickyHeaderViewWrapper<T> viewWrapper;

    @Nonnull
    private Class<T> headerDataClass;

    private List<?> items;

    public StickyHeaderViewManager(@Nonnull View headerView,
                                   @Nonnull RecyclerView recyclerView,
                                   @Nonnull Class<T> headerDataClass,
                                   @Nonnull StickyHeaderViewWrapper<T> viewWrapper) {
        this.headerView = headerView;
        this.viewWrapper = viewWrapper;
        this.recyclerView = recyclerView;
        this.headerDataClass = headerDataClass;
    }

    public void attach(@Nonnull List<?> items) {
        this.items = items;
        if (ViewCompat.isLaidOut(headerView)) {
            bindHeader(recyclerView);
        } else {
            headerView.post(() -> bindHeader(recyclerView));
        }

        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                bindHeader(recyclerView);
            }
        });
    }

    private void bindHeader(RecyclerView recyclerView) {
        if (items.isEmpty()) {
            headerView.setVisibility(View.GONE);
            return;
        } else {
            headerView.setVisibility(View.VISIBLE);
        }

        View topView = recyclerView.getChildAt(0);
        if (topView == null) {
            return;
        }
        int topPosition = recyclerView.getChildAdapterPosition(topView);
        if (!isValidPosition(topPosition)) {
            return;
        }
        if (topPosition == 0 && topView.getTop() == recyclerView.getTop()) {
            headerView.setVisibility(View.GONE);
            return;
        } else {
            headerView.setVisibility(View.VISIBLE);
        }

        T stickyItem;
        Object firstItem = items.get(topPosition);
        if (headerDataClass.isInstance(firstItem)) {
            stickyItem = headerDataClass.cast(firstItem);
            headerView.setTranslationY(0);
        } else {
            stickyItem = findNearestHeader(topPosition);
            int secondPosition = topPosition + 1;
            if (isValidPosition(secondPosition)) {
                Object secondItem = items.get(secondPosition);
                if (headerDataClass.isInstance(secondItem)) {
                    View secondView = recyclerView.getChildAt(1);
                    if (secondView != null) {
                        moveViewFor(secondView);
                    }
                } else {
                    headerView.setTranslationY(0);
                }
            }
        }

        if (stickyItem != null) {
            viewWrapper.bindView(stickyItem);
        }
    }

    private void moveViewFor(View secondView) {
        if (secondView.getTop() <= headerView.getBottom()) {
            headerView.setTranslationY(secondView.getTop() - headerView.getHeight());
        } else {
            headerView.setTranslationY(0);
        }
    }

    private T findNearestHeader(int position) {
        for (int i = position; position >= 0; i--) {
            Object item = items.get(i);
            if (headerDataClass.isInstance(item)) {
                return headerDataClass.cast(item);
            }
        }
        return null;
    }

    private boolean isValidPosition(int position) {
        return !(position == RecyclerView.NO_POSITION || position >= items.size());
    }
}

বাইন্ড শিরোনাম দর্শনটির জন্য ইন্টারফেস।

public interface StickyHeaderViewWrapper<T> {

    void bindView(T data);
}

আমি এই সমাধানটি পছন্দ করি। সন্ধানের নিকটতম হেডারে ছোট টাইপ: for (int i = position; position >= 0; i--){ //should be i >= 0
কনস্টান্টিন

3

ইয়ো,

এটি যদি আপনি কেবল এক ধরণের ধারক স্টিকটি পর্দা থেকে বেরিয়ে আসা শুরু করতে চান তবে আপনি এটি করেন (আমরা কোনও বিভাগের যত্ন নিচ্ছি না)। পুনর্ব্যবহারযোগ্য আইটেমগুলির অভ্যন্তরীণ পুনর্ব্যবহারযোগ্য যুক্তিটি ভাঙ্গা ছাড়াই কেবল একটি উপায় রয়েছে এবং তা হ'ল পুনর্ব্যবহারযোগ্য ভিউর শিরোলেখ আইটেমের উপরে অতিরিক্ত ভিউ সঞ্চারিত করা এবং এতে ডেটা প্রেরণ করা। আমি কোডটি বলতে দেব।

import android.graphics.Canvas
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView

class StickyHeaderItemDecoration(@LayoutRes private val headerId: Int, private val HEADER_TYPE: Int) : RecyclerView.ItemDecoration() {

private lateinit var stickyHeaderView: View
private lateinit var headerView: View

private var sticked = false

// executes on each bind and sets the stickyHeaderView
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
    super.getItemOffsets(outRect, view, parent, state)

    val position = parent.getChildAdapterPosition(view)

    val adapter = parent.adapter ?: return
    val viewType = adapter.getItemViewType(position)

    if (viewType == HEADER_TYPE) {
        headerView = view
    }
}

override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
    super.onDrawOver(c, parent, state)
    if (::headerView.isInitialized) {

        if (headerView.y <= 0 && !sticked) {
            stickyHeaderView = createHeaderView(parent)
            fixLayoutSize(parent, stickyHeaderView)
            sticked = true
        }

        if (headerView.y > 0 && sticked) {
            sticked = false
        }

        if (sticked) {
            drawStickedHeader(c)
        }
    }
}

private fun createHeaderView(parent: RecyclerView) = LayoutInflater.from(parent.context).inflate(headerId, parent, false)

private fun drawStickedHeader(c: Canvas) {
    c.save()
    c.translate(0f, Math.max(0f, stickyHeaderView.top.toFloat() - stickyHeaderView.height.toFloat()))
    headerView.draw(c)
    c.restore()
}

private fun fixLayoutSize(parent: ViewGroup, view: View) {

    // Specs for parent (RecyclerView)
    val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
    val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)

    // Specs for children (headers)
    val childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, parent.paddingLeft + parent.paddingRight, view.getLayoutParams().width)
    val childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, parent.paddingTop + parent.paddingBottom, view.getLayoutParams().height)

    view.measure(childWidthSpec, childHeightSpec)

    view.layout(0, 0, view.measuredWidth, view.measuredHeight)
}

}

এবং তারপরে আপনি নিজের অ্যাডাপ্টারে এটি করুন:

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
    super.onAttachedToRecyclerView(recyclerView)
    recyclerView.addItemDecoration(StickyHeaderItemDecoration(R.layout.item_time_filter, YOUR_STICKY_VIEW_HOLDER_TYPE))
}

যেখানে YOUR_STICKY_VIEW_HOLDER_TYPE হ'ল স্টিকি ধারক বলে মনে হচ্ছে তা আপনার ভিউ টাইপ


2

যারা উদ্বেগ করতে পারে তাদের জন্য। সেবাদাস্তানের উত্তরের ভিত্তিতে, আপনি কি এটি অনুভূমিক স্ক্রোল তৈরি করতে চান। কেবলমাত্র সব পরিবর্তন getBottom()করতে getRight()এবং getTop()করতেgetLeft()


-1

উত্তর ইতিমধ্যে এখানে হয়েছে। আপনি যদি কোনও লাইব্রেরি ব্যবহার করতে না চান তবে আপনি এই পদক্ষেপগুলি অনুসরণ করতে পারেন:

  1. নাম অনুসারে ডেটা সহ তালিকা সাজান
  2. ডেটা সহ তালিকার মাধ্যমে এবং যদি বর্তমানের আইটেমের প্রথম অক্ষর! = পরের আইটেমের প্রথম অক্ষর, তখন "বিশেষ" ধরণের বস্তু সন্নিবেশ করান।
  3. আইটেমটি "বিশেষ" হলে আপনার অ্যাডাপ্টারের জায়গার ভিতরে বিশেষ ভিউ থাকে।

ব্যাখ্যা:

ইন onCreateViewHolderপদ্ধতি আমরা পরীক্ষা করতে পারবেন viewTypeএবং মান (আমাদের "বিশেষ" ধরনের) উপর নির্ভর করে একটি বিশেষ বিন্যাস ফোলান।

উদাহরণ স্বরূপ:

public static final int TITLE = 0;
public static final int ITEM = 1;

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (context == null) {
        context = parent.getContext();
    }
    if (viewType == TITLE) {
        view = LayoutInflater.from(context).inflate(R.layout.recycler_adapter_title, parent,false);
        return new TitleElement(view);
    } else if (viewType == ITEM) {
        view = LayoutInflater.from(context).inflate(R.layout.recycler_adapter_item, parent,false);
        return new ItemElement(view);
    }
    return null;
}

যেখানে class ItemElementএবং class TitleElementসাধারণ দেখতে দেখতে ViewHolder:

public class ItemElement extends RecyclerView.ViewHolder {
//TextView text;

public ItemElement(View view) {
    super(view);
   //text = (TextView) view.findViewById(R.id.text);

}

সুতরাং যে সমস্ত ধারণা আকর্ষণীয়। তবে আমি আগ্রহী যদি এটি কার্যকরভাবে হয়, কারণ আমাদের ডাটা তালিকাটি বাছাই করতে হবে। এবং আমি মনে করি এটি গতি কমিয়ে দেবে। এ সম্পর্কে যদি কোনও চিন্তাভাবনা থাকে তবে দয়া করে আমাকে লিখুন :)

এবং খোলা প্রশ্নটি: কীভাবে উপরে "বিশেষ" লেআউটটি ধরে রাখা যায়, যখন আইটেমগুলি পুনর্ব্যবহার করা হয়। সম্ভবত এর সাথে সমস্ত একত্রিত CoordinatorLayout


এটি কি কার্সরডাপ্টার দিয়ে তৈরি করা সম্ভব
এম

10
এই সমাধানটি স্টিকি শিরোনাম সম্পর্কে কিছু বলতে পারে না যা এই পোস্টের মূল বিষয়
সায়াবশ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.