com.ftinc.kit.attributr.ui.widget.StickyRecyclerHeadersElevationDecoration.java Source code

Java tutorial

Introduction

Here is the source code for com.ftinc.kit.attributr.ui.widget.StickyRecyclerHeadersElevationDecoration.java

Source

/*
 * Copyright (c) 2015 52inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ftinc.kit.attributr.ui.widget;

import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.support.v4.util.LongSparseArray;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.ftinc.kit.attributr.R;
import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersAdapter;

import butterknife.ButterKnife;

public class StickyRecyclerHeadersElevationDecoration extends RecyclerView.ItemDecoration {
    private final StickyRecyclerHeadersAdapter mAdapter;
    private final LongSparseArray<RecyclerView.ViewHolder> mHeaderViews = new LongSparseArray<>();
    private final SparseArray<Rect> mHeaderRects = new SparseArray<>();

    public StickyRecyclerHeadersElevationDecoration(StickyRecyclerHeadersAdapter adapter) {
        mAdapter = adapter;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int orientation = getOrientation(parent);
        int itemPosition = parent.getChildPosition(view);
        if (hasNewHeader(itemPosition)) {
            View header = getHeaderView(parent, itemPosition);
            if (orientation == LinearLayoutManager.VERTICAL) {
                outRect.top = header.getHeight();
            } else {
                outRect.left = header.getWidth();
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(canvas, parent, state);
        int orientation = getOrientation(parent);
        mHeaderRects.clear();

        if (parent.getChildCount() > 0 && mAdapter.getItemCount() > 0) {

            // draw the first visible child's header at the top of the view
            View firstView = parent.getChildAt(0);
            int firstPosition = parent.getChildPosition(firstView);

            if (mAdapter.getHeaderId(firstPosition) > -1) {

                View firstHeader = getHeaderView(parent, firstPosition);
                View nextView = getNextView(parent);

                int translationX = Math.max(parent.getChildAt(0).getLeft() - firstHeader.getWidth(), 0);
                int translationY = Math.max(parent.getChildAt(0).getTop() - firstHeader.getHeight(), 0);
                int nextPosition = parent.getChildPosition(nextView);

                if (nextPosition > 0 && hasNewHeader(nextPosition)) {

                    View secondHeader = getHeaderView(parent, nextPosition);

                    //Translate the topmost header so the next header takes its place, if applicable
                    if (orientation == LinearLayoutManager.VERTICAL
                            && nextView.getTop() - secondHeader.getHeight() - firstHeader.getHeight() < 0) {

                        translationY += nextView.getTop() - secondHeader.getHeight() - firstHeader.getHeight();

                    } else if (orientation == LinearLayoutManager.HORIZONTAL
                            && nextView.getLeft() - secondHeader.getWidth() - firstHeader.getWidth() < 0) {

                        translationX += nextView.getLeft() - secondHeader.getWidth() - firstHeader.getWidth();

                    }
                }

                boolean shouldDrawShadow = true;
                if (firstPosition == 0 && (firstView.getTop() == firstHeader.getHeight())) {
                    shouldDrawShadow = false;
                }

                if (translationY == 0 && shouldDrawShadow) {

                    ImageView shadow = ButterKnife.findById(firstHeader, R.id.shadow);
                    shadow.setVisibility(View.VISIBLE);

                } else {

                    ImageView shadow = ButterKnife.findById(firstHeader, R.id.shadow);
                    shadow.setVisibility(View.GONE);

                }

                canvas.save();
                canvas.translate(translationX, translationY);
                firstHeader.draw(canvas);
                canvas.restore();
                mHeaderRects.put(firstPosition, new Rect(translationX, translationY,
                        translationX + firstHeader.getWidth(), translationY + firstHeader.getHeight()));

            }

            if (parent.getChildCount() > 1 && mAdapter.getItemCount() > 1) {
                for (int i = 1; i < parent.getChildCount(); i++) {

                    int position = parent.getChildPosition(parent.getChildAt(i));
                    if (hasNewHeader(position)) {

                        // this header is different than the previous, it must be drawn in the correct place
                        int translationX = 0;
                        int translationY = 0;
                        View header = getHeaderView(parent, position);

                        if (orientation == LinearLayoutManager.VERTICAL) {

                            translationY = parent.getChildAt(i).getTop() - header.getHeight();

                        } else {

                            translationX = parent.getChildAt(i).getLeft() - header.getWidth();

                        }

                        // don't render shadow
                        ImageView shadow = ButterKnife.findById(header, R.id.shadow);
                        shadow.setVisibility(View.GONE);

                        canvas.save();
                        canvas.translate(translationX, translationY);
                        header.draw(canvas);
                        canvas.restore();
                        mHeaderRects.put(position, new Rect(translationX, translationY,
                                translationX + header.getWidth(), translationY + header.getHeight()));
                    }
                }
            }
        }
    }

    /**
     * Returns the first item currently in the recyclerview that's not obscured by a header.
     * @param parent
     * @return
     */
    private View getNextView(RecyclerView parent) {
        View firstView = parent.getChildAt(0);

        // draw the first visible child's header at the top of the view
        int firstPosition = parent.getChildPosition(firstView);
        View firstHeader = getHeaderView(parent, firstPosition);

        for (int i = 0; i < parent.getChildCount(); i++) {

            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();

            if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {

                if (child.getTop() - layoutParams.topMargin > firstHeader.getHeight()) {
                    return child;
                }

            } else {

                if (child.getLeft() - layoutParams.leftMargin > firstHeader.getWidth()) {
                    return child;
                }

            }
        }
        return null;
    }

    private int getOrientation(RecyclerView parent) {
        if (parent.getLayoutManager() instanceof LinearLayoutManager) {
            LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
            return layoutManager.getOrientation();
        } else {
            throw new IllegalStateException(
                    "StickyListHeadersDecoration can only be used with a " + "LinearLayoutManager.");
        }
    }

    /**
     * Gets the position of the header under the specified (x, y) coordinates.
     * @param x x-coordinate
     * @param y y-coordinate
     * @return position of header, or -1 if not found
     */
    public int findHeaderPositionUnder(int x, int y) {
        for (int i = 0; i < mHeaderRects.size(); i++) {
            Rect rect = mHeaderRects.get(mHeaderRects.keyAt(i));
            if (rect.contains(x, y)) {
                return mHeaderRects.keyAt(i);
            }
        }
        return -1;
    }

    /**
     * Gets the header view for the associated position.  If it doesn't exist yet, it will be
     * created, measured, and laid out.
     * @param parent
     * @param position
     * @return Header view
     */
    public View getHeaderView(RecyclerView parent, int position) {
        long headerId = mAdapter.getHeaderId(position);

        RecyclerView.ViewHolder viewHolder = mHeaderViews.get(headerId);
        if (viewHolder == null) {

            viewHolder = mAdapter.onCreateHeaderViewHolder(parent);
            View header = viewHolder.itemView;
            header.setTag(viewHolder);

            if (header.getLayoutParams() == null) {

                header.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));

            }

            int widthSpec;
            int heightSpec;

            if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
                widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
                heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
            } else {
                widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.UNSPECIFIED);
                heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.EXACTLY);
            }

            int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
                    parent.getPaddingLeft() + parent.getPaddingRight(), header.getLayoutParams().width);
            int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
                    parent.getPaddingTop() + parent.getPaddingBottom(), header.getLayoutParams().height);
            header.measure(childWidth, childHeight);
            header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
            mHeaderViews.put(headerId, viewHolder);
        }

        // Rebind content to the view holder
        mAdapter.onBindHeaderViewHolder(viewHolder, position);

        return viewHolder.itemView;
    }

    private boolean hasNewHeader(int position) {
        if (getFirstHeaderPosition() == position) {
            return true;
        } else if (mAdapter.getHeaderId(position) < 0) {
            return false;
        } else if (position > 0 && position < mAdapter.getItemCount()) {
            return mAdapter.getHeaderId(position) != mAdapter.getHeaderId(position - 1);
        } else {
            return false;
        }
    }

    private int getFirstHeaderPosition() {
        for (int i = 0; i < mAdapter.getItemCount(); i++) {
            if (mAdapter.getHeaderId(i) >= 0) {
                return i;
            }
        }
        return -1;
    }
}