यहां मैं समझाऊंगा कि बाहरी पुस्तकालय के बिना इसे कैसे किया जाए। यह बहुत लंबी पोस्ट होगी, इसलिए खुद को कोसें।
सबसे पहले, मुझे @ tim.paetz को स्वीकार करना चाहिए जिनके पोस्ट ने मुझे ItemDecoration
एस का उपयोग करके अपने स्वयं के चिपचिपा हेडर को लागू करने की यात्रा के लिए प्रेरित किया । मैंने अपने कार्यान्वयन में उनके कोड के कुछ हिस्सों को उधार लिया था।
जैसा कि आप पहले से ही अनुभव कर चुके हैं, यदि आपने इसे स्वयं करने का प्रयास किया है, तो वास्तव में तकनीक के साथ इसे करने के लिए HOW की एक अच्छी व्याख्या खोजना बहुत कठिन है ItemDecoration
। मेरा मतलब है, कदम क्या हैं? इसके पीछे क्या तर्क है? मैं सूची के शीर्ष पर शीर्ष लेख कैसे बना सकता हूँ? इन सवालों के जवाब न जानते हुए कि बाहरी पुस्तकालयों का उपयोग करने के लिए दूसरों को क्या करना पड़ता है, जबकि इसका उपयोग स्वयं ItemDecoration
करना बहुत आसान है।
आरंभिक स्थितियां
- आप डेटासेट
list
विभिन्न प्रकारों के आइटम होने चाहिए ("जावा प्रकार" अर्थ में नहीं, बल्कि "हेडर / आइटम" प्रकार के अर्थ में)।
- आपकी सूची पहले से ही क्रमबद्ध होनी चाहिए।
- सूची में प्रत्येक आइटम कुछ प्रकार का होना चाहिए - इसमें एक हेडर आइटम होना चाहिए।
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+ पंक्तियों को जानते हैं, तब तक आप अपनी पसंद का आइटम नहीं बना सकते और न ही शीर्ष पर रह सकते हैं । इसलिए, जैसा कि यह हमेशा UI डिज़ाइन के साथ जाता है, यदि आप कुछ नहीं बना सकते हैं, तो इसे नकली करें। आप बस शीर्ष लेख का उपयोग करके सब कुछ ऊपर खींचते हैंCanvas
। आपको यह भी पता होना चाहिए कि उपयोगकर्ता इस समय किन वस्तुओं को देख सकता है। यह सिर्फ ऐसा होता है, जो ItemDecoration
आपको Canvas
दृश्य वस्तुओं के बारे में और जानकारी दोनों प्रदान कर सकता है । इसके साथ, यहां मूल चरण हैं:
में onDrawOver
करने की विधि RecyclerView.ItemDecoration
बहुत पहले (ऊपर) आइटम है कि उपयोगकर्ता के लिए दिख रहा है मिलता है।
View topChild = parent.getChildAt(0);
निर्धारित करें कि कौन सा शीर्षलेख इसका प्रतिनिधित्व करता है।
int topChildPosition = parent.getChildAdapterPosition(topChild);
View currentHeader = getHeaderViewForItem(topChildPosition, parent);
drawHeader()
विधि का उपयोग करके RecyclerView के शीर्ष पर उपयुक्त हेडर ड्रा करें ।
मैं आगामी नए शीर्ष लेख को पूरा करने पर व्यवहार को भी लागू करना चाहता हूं: ऐसा प्रतीत होना चाहिए कि आगामी शीर्ष लेख धीरे से शीर्ष वर्तमान शीर्षलेख को दृश्य से बाहर धकेलता है और अंततः उसकी जगह लेता है।
"सब कुछ के ऊपर ड्राइंग" की समान तकनीक यहां लागू होती है।
निर्धारित करें कि शीर्ष "अटक" हेडर नए आगामी एक को कैसे पूरा करता है।
View childInContact = getChildInContact(parent, contactPoint);
इस संपर्क बिंदु को प्राप्त करें (जो कि स्टिक हैडर के नीचे आपके ड्रू और आगामी हेडर के शीर्ष पर है)।
int contactPoint = currentHeader.getBottom();
यदि सूची में आइटम इस "संपर्क बिंदु" को अतिरंजित कर रहा है, तो अपने चिपचिपे शीर्षलेख को फिर से बनाएं ताकि उसका तल ट्रैप्सिंग आइटम के शीर्ष पर हो। आप इसे 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
काम करने के लिए यह करने के लिए। कार्यान्वयन आपके पास मौजूद डेटा पर निर्भर करता है।
अंत में, यहां मैं आधे पारदर्शी हेडर के साथ एक GIF प्रदान करता हूं, ताकि आप विचार को समझ सकें और वास्तव में देख सकते हैं कि हुड के नीचे क्या चल रहा है।
यहाँ "बस सब कुछ के ऊपर आकर्षित" अवधारणा है। आप देख सकते हैं कि दो आइटम "हेडर 1" हैं - एक जिसे हम खींचते हैं और एक अटक स्थिति में शीर्ष पर रहते हैं, और दूसरा वह जो डेटासेट से आता है और बाकी सभी वस्तुओं के साथ चलता है। उपयोगकर्ता को इसके आंतरिक कामकाज दिखाई नहीं देंगे, क्योंकि आपके पास आधे पारदर्शी हेडर नहीं होंगे।
और यहां "पुश आउट" चरण में क्या होता है:
आशा है कि यह मदद की।
संपादित करें
यहाँ getHeaderPositionForItem()
RecyclerView के एडाप्टर में विधि का वास्तविक कार्यान्वयन है :
@Override
public int getHeaderPositionForItem(int itemPosition) {
int headerPosition = 0;
do {
if (this.isHeader(itemPosition)) {
headerPosition = itemPosition;
break;
}
itemPosition -= 1;
} while (itemPosition >= 0);
return headerPosition;
}
कोटलिन में थोड़ा अलग कार्यान्वयन