io.github.sin3hz.fastjumper.sample.PhotoListFragment.java Source code

Java tutorial

Introduction

Here is the source code for io.github.sin3hz.fastjumper.sample.PhotoListFragment.java

Source

/*
 * Copyright (C) 2016 sin3hz
 *
 * 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 io.github.sin3hz.fastjumper.sample;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;

import java.io.File;
import java.util.Calendar;

import io.github.sin3hz.fastjumper.FastJumper;
import io.github.sin3hz.fastjumper.callback.LinearScrollCalculator;
import io.github.sin3hz.fastjumper.callback.SpannableCallback;
import io.github.sin3hz.fastjumper.callback.StaggeredScrollCalculator;
import io.github.sin3hz.fastjumper.sample.utils.CursorRecyclerAdapter;
import io.github.sin3hz.fastjumper.sample.utils.RatioFrameLayout;
import io.github.sin3hz.fastjumper.sample.utils.SectionCursor;
import io.github.sin3hz.fastjumper.utils.RecyclerViewHelper;

public class PhotoListFragment extends Fragment implements MainActivity.Listener {

    private static final String TAG = PhotoListFragment.class.getSimpleName();
    private static final boolean DEBUG = false;

    private static final int LOADER_PHOTO = 1;

    public static PhotoListFragment newInstance() {
        PhotoListFragment fragment = new PhotoListFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    private View mEmptyView;
    private View mContentView;
    private RecyclerView mRecyclerView;
    private SwipeRefreshLayout mSwipeRefreshLayout;
    private FloatingActionButton mFab;

    private PhotoListAdapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;

    private int mItemWidth;
    private int mSpanCount;

    private FastJumper mFastJumper;
    private SpannableCallback mJumperCallback;

    private int mSortOrder = R.id.action_sort_date;
    private static final String SORT_ORDER_DATE = MediaStore.Images.Media.DATE_TAKEN + " DESC";
    private static final String SORT_ORDER_FOLDER = MediaStore.Images.Media.BUCKET_ID;

    private int mLayoutManagerType = R.id.action_layout_grid;
    private static final String STATE_KEY_LM_TYPE = "STATE_KEY_LM_TYPE";
    private static final String STATE_KEY_SORT_ORDER = "STATE_KEY_SORT_ORDER";
    private static final String STATE_KEY_SECTION = "STATE_KEY_SECTION";

    private int mSection = R.id.action_section_alphabet;

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(STATE_KEY_LM_TYPE, mLayoutManagerType);
        outState.putInt(STATE_KEY_SORT_ORDER, mSortOrder);
        outState.putInt(STATE_KEY_SECTION, mSection);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        if (savedInstanceState != null) {
            mLayoutManagerType = savedInstanceState.getInt(STATE_KEY_LM_TYPE);
            mSortOrder = savedInstanceState.getInt(STATE_KEY_SORT_ORDER);
            mSection = savedInstanceState.getInt(STATE_KEY_SECTION);
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_photo_list, container, false);
        mEmptyView = view.findViewById(R.id.empty);
        mContentView = view.findViewById(R.id.content);
        mRecyclerView = (RecyclerView) view.findViewById(R.id.list);
        mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe);
        mFab = (FloatingActionButton) getActivity().findViewById(R.id.fab);
        return view;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mSwipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                getLoaderManager().restartLoader(0, null, mLoaderCallbacks);
            }
        });
        mAdapter = new PhotoListAdapter();
        final ViewTreeObserver vto = mRecyclerView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int width = mRecyclerView.getWidth() - mRecyclerView.getPaddingLeft()
                        - mRecyclerView.getPaddingRight();
                if (width <= 0) {
                    return;
                }
                mRecyclerView.setAdapter(mAdapter);
                mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                setupLayoutManager();
            }
        });
        setupFastJumper();
    }

    private SpannableCallback.ScrollCalculator mLinearScrollCalculator;
    private SpannableCallback.ScrollCalculator mStaggeredScrollCalculator;
    private SpannableCallback.ScrollCalculator mScrollCalculator;

    private void setupFastJumper() {
        mLinearScrollCalculator = new LinearScrollCalculator(mRecyclerView) {

            @Override
            public int getItemHeight(int position) {
                return mAdapter.getItemHeight(position);
            }

            @Override
            public int getSpanSize(int position) {
                return mAdapter.getSpanSize(position);
            }

            @Override
            public int getSpanCount() {
                return mSpanCount;
            }
        };
        mStaggeredScrollCalculator = new StaggeredScrollCalculator(mRecyclerView) {
            @Override
            public int getItemHeight(int position) {
                return mAdapter.getItemHeight(position);
            }

            @Override
            public int getSpanSize(int position) {
                return mAdapter.getSpanSize(position);
            }

            @Override
            public int getSpanCount() {
                return mSpanCount;
            }

        };
        mJumperCallback = new SpannableCallback() {
            @Override
            public boolean isSectionEnable() {
                return true;
            }

            @Override
            public String getSection(int position) {
                return mAdapter.getSectionForPosition(position);
            }

            @Override
            public boolean isEnabled() {
                return true;
            }
        };
        mFastJumper = new FastJumper(mJumperCallback);
        mFastJumper.addListener(new FastJumper.Listener() {
            int fabTop = -1;

            @Override
            public void onScrolled(float progress) {
                if (!mFab.isShown() && fabTop == -1) {
                    return;
                }
                if (fabTop == -1) {
                    int[] rvLocation = new int[2];
                    int[] fabLocation = new int[2];
                    mRecyclerView.getLocationInWindow(rvLocation);
                    mFab.getLocationInWindow(fabLocation);
                    fabTop = fabLocation[1] - rvLocation[1] - mRecyclerView.getPaddingTop();
                }
                int jumperBottom = (int) (progress * (mRecyclerView.getHeight() - mRecyclerView.getPaddingTop()
                        - mRecyclerView.getPaddingBottom()));
                if (jumperBottom > fabTop) {
                    hideFab();
                } else {
                    showFab();
                }
                Log.d(TAG, "progress=" + progress + " fabTop=" + fabTop + " jumperBottom=" + jumperBottom);
            }

            @Override
            public void onStateChange(int state) {
                Log.d(TAG, "onStateChange state=" + state);
                if (state == FastJumper.STATE_GONE) {
                    showFab();
                }
            }
        });
    }

    private void setupSortOrder() {
        getLoaderManager().restartLoader(LOADER_PHOTO, null, mLoaderCallbacks);
    }

    private void setupLayoutManager() {
        mFastJumper.attachToRecyclerView(null);
        switch (mLayoutManagerType) {
        case R.id.action_layout_linear: {
            setupLinearLayoutManager();
            break;
        }
        case R.id.action_layout_grid: {
            setupGridLayoutManager();
            break;
        }
        case R.id.action_layout_staggered_grid: {
            setupStaggeredGridLayoutManager();
            break;
        }
        }
        mRecyclerView.setLayoutManager(mLayoutManager);
        mJumperCallback.setScrollCalculator(mScrollCalculator);
        mFastJumper.attachToRecyclerView(mRecyclerView);
        mFastJumper.invalidate();
    }

    private void calculateSpan(int maxItemWidth) {
        int numColumns = 1;
        int width = mRecyclerView.getWidth();
        while (true) {
            if (width / numColumns > maxItemWidth) {
                ++numColumns;
            } else {
                break;
            }
        }
        int spacing = 0;
        mItemWidth = (width - spacing * (numColumns - 1)) / numColumns;
        mSpanCount = numColumns;
    }

    private void setupLinearLayoutManager() {
        mLayoutManager = new LinearLayoutManager(getContext());
        calculateSpan(mRecyclerView.getWidth());
        mScrollCalculator = mLinearScrollCalculator;
    }

    private void setupGridLayoutManager() {
        calculateSpan(getResources().getDimensionPixelSize(R.dimen.photo_grid_size));
        GridLayoutManager glm = new GridLayoutManager(getContext(), mSpanCount);
        glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return mAdapter.getSpanSize(position);
            }
        });
        mLayoutManager = glm;
        mScrollCalculator = mLinearScrollCalculator;
    }

    private void setupStaggeredGridLayoutManager() {
        calculateSpan(getResources().getDimensionPixelSize(R.dimen.photo_grid_size));
        StaggeredGridLayoutManager sglm = new StaggeredGridLayoutManager(mSpanCount,
                StaggeredGridLayoutManager.VERTICAL);
        mLayoutManager = sglm;
        mScrollCalculator = mStaggeredScrollCalculator;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(LOADER_PHOTO, null, mLoaderCallbacks);
    }

    private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallbacks = new LoaderManager.LoaderCallbacks<Cursor>() {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            String sort;
            if (mSortOrder == R.id.action_sort_date) {
                sort = SORT_ORDER_DATE;
            } else {
                sort = SORT_ORDER_FOLDER;
            }
            return new PhotoLoader(getContext(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null,
                    sort);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            mSwipeRefreshLayout.setRefreshing(false);
            mAdapter.swapCursor(data);
            if (data == null || data.getCount() == 0) {
                showEmptyView();
            } else {
                showContentView();
            }
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            mAdapter.swapCursor(null);
        }
    };

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_photo_list, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        menu.findItem(mLayoutManagerType).setChecked(true);
        menu.findItem(mSortOrder).setChecked(true);
        menu.findItem(mSection).setChecked(true);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_layout_linear:
        case R.id.action_layout_grid:
        case R.id.action_layout_staggered_grid: {
            if (mLayoutManagerType != item.getItemId()) {
                mLayoutManagerType = item.getItemId();
                setupLayoutManager();
            }
            return true;
        }
        case R.id.action_sort_date:
        case R.id.action_sort_folder: {
            if (mSortOrder != item.getItemId()) {
                mSortOrder = item.getItemId();
                setupSortOrder();
            }
            return true;
        }
        case R.id.action_section_index:
        case R.id.action_section_alphabet: {
            mSection = item.getItemId();
            return true;
        }
        }
        return super.onOptionsItemSelected(item);
    }

    private static class PhotoLoader extends CursorLoader {

        class FolderSectionCursor extends SectionCursor<String> {

            public FolderSectionCursor(Cursor cursor) {
                super(cursor);
            }

            @Override
            public String createSection(int position) {
                getWrappedCursor().moveToPosition(position);
                String filePath = getWrappedCursor()
                        .getString(getWrappedCursor().getColumnIndex(MediaStore.Images.Media.DATA));
                File file = new File(filePath);
                return file.getParentFile().getName();
            }
        }

        class DateSectionCursor extends SectionCursor<String> {

            public DateSectionCursor(Cursor cursor) {
                super(cursor);
            }

            @Override
            public String createSection(int position) {
                getWrappedCursor().moveToPosition(position);
                long dateAdded = getWrappedCursor()
                        .getLong(getWrappedCursor().getColumnIndex(MediaStore.Images.Media.DATE_TAKEN));
                return formatDateByMonth(getContext(), dateAdded);
            }

            public String formatDateByMonth(Context context, long timeMillis) {
                Calendar current = Calendar.getInstance();
                current.setTimeInMillis(System.currentTimeMillis());

                Calendar date = Calendar.getInstance();
                date.setTimeInMillis(timeMillis);

                if (current.get(Calendar.YEAR) == date.get(Calendar.YEAR)) {
                    return DateUtils.formatDateTime(context, timeMillis, DateUtils.FORMAT_NO_MONTH_DAY);
                } else {
                    return DateUtils.formatDateTime(context, timeMillis,
                            DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_MONTH_DAY);
                }
            }

        }

        private String mSortOrder;

        public PhotoLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs,
                String sortOrder) {
            super(context, uri, projection, selection, selectionArgs, sortOrder);
            mSortOrder = sortOrder;
        }

        @Override
        public Cursor loadInBackground() {
            Cursor cursor;
            try {
                cursor = super.loadInBackground();
            } catch (SecurityException e) {
                cursor = null;
            }
            if (cursor != null) {
                if (SORT_ORDER_DATE.equals(mSortOrder)) {
                    cursor = new DateSectionCursor(cursor);
                } else if (SORT_ORDER_FOLDER.equals(mSortOrder)) {
                    cursor = new FolderSectionCursor(cursor);
                } else {
                    throw new IllegalStateException("Not support sort by " + mSortOrder);
                }

            }
            return cursor;
        }
    }

    private static class Photo {
        String path;
        int width;
        int height;
    }

    private class PhotoListAdapter extends CursorRecyclerAdapter<RecyclerView.ViewHolder> {

        public static final int VIEW_TYPE_ITEM = 0;
        public static final int VIEW_TYPE_SECTION = 1;

        private int mSubHeaderHeight = getResources().getDimensionPixelSize(R.dimen.section_height);

        class ViewHolder extends RecyclerView.ViewHolder {

            RatioFrameLayout item;
            ImageView thumb;
            TextView title;

            public ViewHolder(View itemView) {
                super(itemView);
                thumb = (ImageView) itemView.findViewById(R.id.thumbnail);
                title = (TextView) itemView.findViewById(R.id.text);
                item = (RatioFrameLayout) itemView;
            }
        }

        class SectionViewHolder extends RecyclerView.ViewHolder {

            TextView title;

            public SectionViewHolder(View itemView) {
                super(itemView);
                title = (TextView) itemView.findViewById(R.id.text1);
            }
        }

        public PhotoListAdapter() {
            super(null);
            setHasStableIds(true);
        }

        @Override
        public void onBindViewHolderCursor(RecyclerView.ViewHolder holder, Cursor cursor) {
            int viewType = getItemViewType(cursor.getPosition());
            switch (viewType) {
            case VIEW_TYPE_ITEM: {
                ViewHolder vh = (ViewHolder) holder;
                vh.item.setHeightRatio(getItemRatio(holder.getAdapterPosition()));

                Photo photo = getItem(holder.getAdapterPosition());
                String url = "file:" + photo.path;
                Glide.with(getContext()).load(url).fitCenter().into(vh.thumb);
                vh.title.setText(String.valueOf(vh.getAdapterPosition()));
                vh.title.setVisibility(DEBUG ? View.VISIBLE : View.GONE);
                break;
            }
            case VIEW_TYPE_SECTION: {
                if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) holder.itemView
                            .getLayoutParams();
                    if (layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
                        ((StaggeredGridLayoutManager.LayoutParams) layoutParams).setFullSpan(true);
                    }
                }
                SectionViewHolder vh = (SectionViewHolder) holder;
                @SuppressWarnings("unchecked")
                String section = ((SectionCursor<String>) cursor).getSection(cursor.getPosition());
                vh.title.setText(section);
                break;
            }
            }
        }

        @Override
        public void onViewRecycled(RecyclerView.ViewHolder holder) {
            super.onViewRecycled(holder);
        }

        @Override
        public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
            return super.onFailedToRecycleView(holder);
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            switch (viewType) {
            case VIEW_TYPE_ITEM: {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_photo, parent,
                        false);
                return new ViewHolder(view);
            }
            case VIEW_TYPE_SECTION: {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_header, parent,
                        false);
                return new SectionViewHolder(view);
            }
            }
            return null;
        }

        @SuppressWarnings("unchecked")
        @Override
        public SectionCursor<String> getCursor() {
            return (SectionCursor<String>) super.getCursor();
        }

        @Override
        public int getItemViewType(int position) {
            getCursor().moveToPosition(position);
            boolean isSection = getCursor().isSection(position);
            return isSection ? VIEW_TYPE_SECTION : VIEW_TYPE_ITEM;
        }

        public Photo getItem(int position) {
            Cursor cursor = getCursor();
            cursor.moveToPosition(position);
            if (COLUMN_INDEX_PATH == -1) {
                COLUMN_INDEX_PATH = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
                COLUMN_INDEX_WIDTH = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH);
                COLUMN_INDEX_HEIGHT = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT);
            }
            Photo photo = new Photo();
            photo.path = cursor.getString(COLUMN_INDEX_PATH);
            photo.height = cursor.getInt(COLUMN_INDEX_HEIGHT);
            photo.width = cursor.getInt(COLUMN_INDEX_WIDTH);
            return photo;
        }

        private int COLUMN_INDEX_PATH = -1;
        private int COLUMN_INDEX_WIDTH = -1;
        private int COLUMN_INDEX_HEIGHT = -1;

        @Override
        public long getItemId(int position) {
            if (getItemViewType(position) == VIEW_TYPE_SECTION) {
                return getSectionForPosition(position).hashCode();
            }
            return super.getItemId(position);
        }

        public String getSectionForPosition(int position) {
            if (position < 0) {
                position = 0;
            }
            if (position >= getItemCount()) {
                position = getItemCount() - 1;
            }
            if (mSection == R.id.action_section_alphabet) {
                return getDateOrPathSection(position);
            } else {
                return getIndexSection(position);
            }
        }

        public String getDateOrPathSection(int position) {
            SectionCursor<String> cursor = getCursor();
            cursor.moveToPosition(position);
            return cursor.getSection(position);
        }

        public String getIndexSection(int position) {
            return "#" + position;
        }

        private float getItemRatio(int position) {
            if (mLayoutManagerType == R.id.action_layout_linear) {
                return 0.5f;
            } else if (mLayoutManagerType == R.id.action_layout_grid) {
                return 1;
            } else if (mLayoutManagerType == R.id.action_layout_staggered_grid) {
                Photo photo = getItem(position);
                return photo.height / (float) photo.width;
            }
            return 0;
        }

        public int getItemHeight(int position) {
            int viewType = mAdapter.getItemViewType(position);
            if (viewType == PhotoListAdapter.VIEW_TYPE_ITEM) {
                return (int) (mItemWidth * getItemRatio(position));
            } else {
                return mSubHeaderHeight;
            }
        }

        public int getSpanSize(int position) {
            if (getItemViewType(position) == VIEW_TYPE_SECTION) {
                return mSpanCount;
            } else {
                return 1;
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    private void showContentView() {
        mContentView.setVisibility(View.VISIBLE);
        mEmptyView.setVisibility(View.GONE);
    }

    private void showEmptyView() {
        mContentView.setVisibility(View.GONE);
        mEmptyView.setVisibility(View.VISIBLE);
    }

    private boolean mPageScrolling;

    @Override
    public void onAppBarCollapse(boolean isCollapsed) {
        mSwipeRefreshLayout.setEnabled(!isCollapsed);
    }

    @Override
    public void onPageScrolling(boolean scrolling) {
        if (scrolling) {
            mFastJumper.hide();
        }
        mPageScrolling = scrolling;
    }

    @Override
    public void onScrollToTop() {
        RecyclerViewHelper.fastSmoothScrollToPosition(mRecyclerView, 0);
        mRecyclerView.post(new Runnable() {
            @Override
            public void run() {
                mFastJumper.invalidate();
            }
        });
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (mFab != null) {
            showFab();
        }
    }

    private void showFab() {
        if (!mPageScrolling && getUserVisibleHint()) {
            mFab.show();
        }
    }

    private void hideFab() {
        mFab.hide();
    }
}