Burada harici bir kütüphane olmadan nasıl yapılacağını açıklayacağım. Çok uzun bir görev olacak, bu yüzden kendinizi destekleyin.
Her şeyden önce, gönderisiItemDecoration
s kullanarak kendi yapışkan başlıklarımı uygulama yolculuğuna çıkmam için bana ilham veren @ tim.paetz'i kabul etmeme izin verin . Uygulamamda onun kodunun bazı kısımlarını ödünç aldım.
Zaten deneyimlemiş olabileceğiniz gibi, bunu kendiniz yapmaya kalkıştıysanız, bunu teknikle nasıl yapacağınıza dair iyi bir açıklama bulmak çok zordur ItemDecoration
. Demek istediğim, adımlar nelerdir? Bunun arkasındaki mantık nedir? Başlığı listenin en üstüne nasıl yapıştırırım? Bu soruların cevaplarını bilmemek, başkalarını harici kitaplıkları kullanmaya iten şeydir, ancak bunu kendi başınıza yapmak ItemDecoration
oldukça kolaydır.
Başlangıç koşulları
- Veri kümeniz
list
farklı türde öğelerden oluşmalıdır ("Java türleri" anlamında değil, "başlık / öğe" türleri anlamında).
- Listeniz önceden sıralanmış olmalıdır.
- Listedeki her öğe belirli türde olmalıdır - bununla ilgili bir başlık öğesi olmalıdır.
- İçindeki ilk öğe
list
bir başlık öğesi olmalıdır.
Benim için tam kodu sağlar Buraya RecyclerView.ItemDecoration
denir HeaderItemDecoration
. Ardından atılan adımları detaylı bir şekilde anlatıyorum.
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);
}
}
İş mantığı
Öyleyse, bunu nasıl yapıştırabilirim?
Yapmıyorsun. Bir yapamazsınız RecyclerView
özel düzenlerden bir guru olan ve bir için kod 12.000'den satırları bilmedikçe, üstte 'ın sadece durak seçtiğiniz öğeyi ve sopa RecyclerView
ezbere. Yani, her zaman olduğu gibi UI tasarımında olduğu gibi, bir şey yapamıyorsanız, sahte yapın. Sen sadece her şeyin üstünde başlık çizmek kullanarak Canvas
. Ayrıca kullanıcının o anda hangi öğeleri görebileceğini de bilmelisiniz. Sadece, olur ItemDecoration
hem sağlayabilir Canvas
görünür öğeleri hakkında ve bilgiler. Bununla birlikte, işte temel adımlar:
In onDrawOver
yöntemine RecyclerView.ItemDecoration
kullanıcının görebildiği ilk (üst) öğesini olsun.
View topChild = parent.getChildAt(0);
Hangi başlığın onu temsil ettiğini belirleyin.
int topChildPosition = parent.getChildAdapterPosition(topChild);
View currentHeader = getHeaderViewForItem(topChildPosition, parent);
drawHeader()
Yöntemi kullanarak RecyclerView'ın üstüne uygun başlığı çizin .
Ayrıca, yeni gelecek başlık en üstteki ile karşılaştığında davranışı uygulamak istiyorum: yaklaşan başlık nazikçe görünümün dışına itiyor ve sonunda yerini alıyor gibi görünmelidir.
Aynı "her şeyin üstüne çizim" tekniği burada da geçerlidir.
En üstteki "sıkışmış" başlığın yeni gelecek olanla ne zaman karşılaştığını belirleyin.
View childInContact = getChildInContact(parent, contactPoint);
Bu temas noktasını alın (bu, çizdiğiniz yapışkan başlığın alt kısmı ve yaklaşan başlığın üstü).
int contactPoint = currentHeader.getBottom();
Listedeki öğe bu "temas noktasını" ihlal ediyorsa, yapışkan başlığınızı yeniden çizin, böylece alt kısmı izinsiz giren öğenin en üstünde olur. Sen ile bunu başarmak translate()
yöntemi Canvas
. Sonuç olarak, üst başlığın başlangıç noktası görünür alanın dışında olacak ve "yaklaşan başlık tarafından dışarı atılmış" gibi görünecektir. Tamamen bittiğinde, yeni başlığı üstüne çizin.
if (childInContact != null) {
if (mListener.isHeader(parent.getChildAdapterPosition(childInContact))) {
moveHeader(c, currentHeader, childInContact);
} else {
drawHeader(c, currentHeader);
}
}
Geri kalanı, sağladığım kod parçasındaki yorumlar ve ayrıntılı açıklamalarla açıklandı.
Kullanım basittir:
mRecyclerView.addItemDecoration(new HeaderItemDecoration((HeaderItemDecoration.StickyHeaderInterface) mAdapter));
Sizin mAdapter
uygulaması gereken StickyHeaderInterface
çalışmalara bunun için. Uygulama, sahip olduğunuz verilere bağlıdır.
Son olarak, burada yarı saydam başlıklara sahip bir gif veriyorum, böylece fikri kavrayabilir ve kaputun altında neler olup bittiğini gerçekten görebilirsiniz.
İşte "her şeyin üstüne çizin" konseptinin örneği. İki öğe "başlık 1" olduğunu görebilirsiniz - biri çizip üstte takılı bir konumda kalır, diğeri ise veri kümesinden gelir ve geri kalan tüm öğelerle birlikte hareket eder. Yarı saydam başlıklara sahip olmayacağınız için kullanıcı onun iç işleyişini görmez.
Ve burada "itme" aşamasında olanlar:
Umarım yardımcı olmuştur.
Düzenle
İşte getHeaderPositionForItem()
RecyclerView adaptöründeki gerçek yöntem uygulamam :
@Override
public int getHeaderPositionForItem(int itemPosition) {
int headerPosition = 0;
do {
if (this.isHeader(itemPosition)) {
headerPosition = itemPosition;
break;
}
itemPosition -= 1;
} while (itemPosition >= 0);
return headerPosition;
}
Kotlin'de biraz farklı uygulama