Преглед изворни кода

Added sticky headers implementation

Vadik Sirekanyan пре 7 година
родитељ
комит
ddc6cfe575

+ 91 - 0
app/src/main/java/me/vadik/knigopis/common/HeaderItemDecoration.java

@@ -0,0 +1,91 @@
+package me.vadik.knigopis.common;
+
+import android.graphics.Canvas;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * https://stackoverflow.com/questions/32949971
+ */
+public class HeaderItemDecoration extends RecyclerView.ItemDecoration {
+
+    private StickyHeaderInterface mListener;
+
+    public HeaderItemDecoration(@NonNull StickyHeaderInterface listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        super.onDrawOver(c, parent, state);
+        View topChild = parent.getChildAt(0);
+        if (topChild == null) {
+            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 (childInContact == null) {
+            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) {
+                    childInContact = child;
+                    break;
+                }
+            }
+        }
+        return childInContact;
+    }
+
+    private void fixLayoutSize(ViewGroup parent, View view) {
+        int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
+        int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
+        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(), view.getMeasuredHeight());
+    }
+
+}

+ 39 - 0
app/src/main/java/me/vadik/knigopis/common/StickyHeaderInterface.java

@@ -0,0 +1,39 @@
+package me.vadik.knigopis.common;
+
+import android.view.View;
+
+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);
+}

+ 41 - 4
app/src/main/java/me/vadik/knigopis/user/UserActivity.kt

@@ -8,15 +8,18 @@ import android.support.v7.app.AppCompatActivity
 import android.support.v7.widget.LinearLayoutManager
 import android.view.Menu
 import android.view.MenuItem
+import android.view.View
+import android.widget.TextView
 import kotlinx.android.synthetic.main.user_activity.*
 import me.vadik.knigopis.*
 import me.vadik.knigopis.adapters.books.BooksAdapter
-import me.vadik.knigopis.utils.setCircleImage
+import me.vadik.knigopis.common.HeaderItemDecoration
+import me.vadik.knigopis.common.StickyHeaderInterface
 import me.vadik.knigopis.dialog.DialogFactory
 import me.vadik.knigopis.model.Book
-import me.vadik.knigopis.utils.snackbar
-import me.vadik.knigopis.utils.systemClipboardManager
-import me.vadik.knigopis.utils.toast
+import me.vadik.knigopis.model.BookHeader
+import me.vadik.knigopis.model.FinishedBook
+import me.vadik.knigopis.utils.*
 import org.koin.android.ext.android.inject
 
 private const val EXTRA_USER_ID = "me.vadik.knigopis.extra_user_id"
@@ -58,6 +61,40 @@ class UserActivity : AppCompatActivity() {
         supportActionBar?.setDisplayHomeAsUpEnabled(true)
         val layoutManager = LinearLayoutManager(this)
         userBooksRecyclerView.layoutManager = layoutManager
+        userBooksRecyclerView.addItemDecoration(
+            HeaderItemDecoration(
+                object : StickyHeaderInterface {
+                    override fun getHeaderPositionForItem(itemPosition: Int): Int {
+                        return itemPosition
+                    }
+
+                    override fun getHeaderLayout(headerPosition: Int): Int {
+                        return R.layout.header
+                    }
+
+                    override fun bindHeaderData(header: View, headerPosition: Int) {
+                        val book = books[headerPosition]
+                        val title = if (book is FinishedBook) {
+                            book.readYear
+                        } else {
+                            book.title
+                        }.let {
+                            if (it.isEmpty()) {
+                                getString(R.string.books_header_done_other)
+                            } else {
+                                it
+                            }
+                        }
+                        header.findViewById<TextView>(R.id.book_title).text = title
+                        header.findViewById<View>(R.id.header_bottom_divider).showNow()
+                    }
+
+                    override fun isHeader(itemPosition: Int): Boolean {
+                        return books[itemPosition] is BookHeader
+                    }
+                }
+            )
+        )
 //        userBooksRecyclerView.addItemDecoration(
 //            DividerItemDecoration(this, layoutManager.orientation)
 //        )

+ 10 - 1
app/src/main/res/layout/header.xml

@@ -2,7 +2,8 @@
 <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="48dp">
+    android:layout_height="48dp"
+    android:background="@color/white">
 
     <View
         android:id="@+id/header_divider"
@@ -23,4 +24,12 @@
         android:textSize="14sp"
         tools:text="К прочтению" />
 
+    <View
+        android:id="@+id/header_bottom_divider"
+        android:layout_width="match_parent"
+        android:layout_height="0.5dp"
+        android:layout_gravity="bottom"
+        android:background="@color/recycler_divider_color"
+        android:visibility="invisible" />
+
 </FrameLayout>

+ 1 - 2
app/src/main/res/layout/user_activity.xml

@@ -61,8 +61,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:clipToPadding="false"
-        android:paddingBottom="8dp"
-        android:paddingTop="8dp"
+        android:paddingTop="0.5dp"
         app:layout_behavior="@string/appbar_scrolling_view_behavior"
         tools:listitem="@layout/user_book" />