Java tutorial
/* * 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(); } }