Java tutorial
/* * Copyright (c) 2016. Matsuda, Akihit (akihito104) * * 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.freshdigitable.udonroad; import android.content.Context; import android.content.Intent; import android.databinding.DataBindingUtil; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import com.freshdigitable.udonroad.StatusViewBase.OnUserIconClickedListener; import com.freshdigitable.udonroad.TimelineAdapter.LastItemBoundListener; import com.freshdigitable.udonroad.TimelineAdapter.OnSelectedEntityChangeListener; import com.freshdigitable.udonroad.databinding.FragmentTimelineBinding; import com.freshdigitable.udonroad.datastore.SortedCache; import rx.Subscription; import rx.functions.Action1; import twitter4j.Paging; import twitter4j.User; /** * TimelineFragment provides RecyclerView to show timeline. * * Created by Akihit. */ public class TimelineFragment<T> extends Fragment { @SuppressWarnings("unused") private static final String TAG = TimelineFragment.class.getSimpleName(); public static final String BUNDLE_IS_SCROLLED_BY_USER = "is_scrolled_by_user"; public static final String BUNDLE_STOP_SCROLL = "stop_scroll"; private FragmentTimelineBinding binding; private TimelineAdapter<T> tlAdapter; private LinearLayoutManager tlLayoutManager; private Subscription insertEventSubscription; private Subscription deleteEventSubscription; private SortedCache<T> timelineStore; @Override public void onAttach(Context context) { super.onAttach(context); } @Override public void onDetach() { insertEventSubscription.unsubscribe(); deleteEventSubscription.unsubscribe(); super.onDetach(); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.timeline, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { final int itemId = item.getItemId(); if (itemId == R.id.action_heading) { scrollToTop(); } return false; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate(inflater, R.layout.fragment_timeline, container, false); if (savedInstanceState != null) { isScrolledByUser = savedInstanceState.getBoolean(BUNDLE_IS_SCROLLED_BY_USER); stopScroll = savedInstanceState.getBoolean(BUNDLE_STOP_SCROLL); } return binding.getRoot(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(BUNDLE_IS_SCROLLED_BY_USER, isScrolledByUser); outState.putBoolean(BUNDLE_STOP_SCROLL, stopScroll); } private boolean isScrolledByUser = false; @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); binding.timeline.setHasFixedSize(true); binding.timeline.addItemDecoration(new TimelineDecoration(getContext())); tlLayoutManager = new LinearLayoutManager(getContext()); tlLayoutManager.setAutoMeasureEnabled(true); binding.timeline.setLayoutManager(tlLayoutManager); binding.timeline.setItemAnimator(new TimelineAnimator()); binding.timeline.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // Log.d(TAG, "onTouch: " + event.getAction()); if (event.getAction() == MotionEvent.ACTION_UP) { final int firstVisibleItemPosition = tlLayoutManager.findFirstVisibleItemPosition(); isScrolledByUser = firstVisibleItemPosition != 0; isAddedUntilStopped(); } return false; } }); tlAdapter = new TimelineAdapter<>(timelineStore); insertEventSubscription = timelineStore.observeInsertEvent().subscribe(new Action1<Integer>() { @Override public void call(Integer position) { tlAdapter.notifyItemInserted(position); } }); deleteEventSubscription = timelineStore.observeDeleteEvent().subscribe(new Action1<Integer>() { @Override public void call(Integer position) { tlAdapter.notifyItemRemoved(position); } }); if (getActivity() instanceof FabHandleable) { tlAdapter.setOnSelectedEntityChangeListener(new OnSelectedEntityChangeListener() { @Override public void onEntitySelected(long entityId) { showFab(); } @Override public void onEntityUnselected() { hideFab(); } }); } tlAdapter.setLastItemBoundListener(new LastItemBoundListener() { @Override public void onLastItemBound(long lastPageCursor) { fetchTweet(new Paging(1, 20, 1, lastPageCursor)); } }); final OnUserIconClickedListener userIconClickedListener = createUserIconClickedListener(); tlAdapter.setOnUserIconClickedListener(userIconClickedListener); binding.timeline.setAdapter(tlAdapter); fetchTweet(null); } private final RecyclerView.AdapterDataObserver itemInsertedObserver = new RecyclerView.AdapterDataObserver() { @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); if (positionStart != 0) { return; } if (canScroll()) { // Log.d(TAG, "onItemRangeInserted: "); scrollTo(0); } else { addedUntilStopped = true; } } }; private final RecyclerView.AdapterDataObserver createdAtObserver = new RecyclerView.AdapterDataObserver() { @Override public void onItemRangeChanged(int positionStart, int itemCount) { super.onItemRangeChanged(positionStart, itemCount); updateTime(); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); updateTime(); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { super.onItemRangeRemoved(positionStart, itemCount); updateTime(); } private void updateTime() { final int childCount = binding.timeline.getChildCount(); for (int i = 0; i < childCount; i++) { final View v = binding.timeline.getChildAt(i); ((StatusView) v).updateTime(); } } }; @Override public void onStart() { super.onStart(); tlAdapter.registerAdapterDataObserver(itemInsertedObserver); tlAdapter.registerAdapterDataObserver(createdAtObserver); isAddedUntilStopped(); if (isVisible()) { if (tlAdapter.isStatusViewSelected()) { showFab(); } else { hideFab(); } } } public long getSelectedTweetId() { return tlAdapter.getSelectedEntityId(); } @Override public void onStop() { Log.d(TAG, "onStop: "); super.onStop(); tlAdapter.unregisterAdapterDataObserver(itemInsertedObserver); tlAdapter.unregisterAdapterDataObserver(createdAtObserver); } @Override public void onDestroyView() { super.onDestroyView(); tlAdapter.setLastItemBoundListener(null); tlAdapter.setOnSelectedEntityChangeListener(null); tlAdapter.setOnUserIconClickedListener(null); binding.timeline.setOnTouchListener(null); binding.timeline.setAdapter(null); } private void showFab() { final FragmentActivity activity = getActivity(); if (activity instanceof FabHandleable) { ((FabHandleable) activity).showFab(); } } private void hideFab() { final FragmentActivity activity = getActivity(); if (activity instanceof FabHandleable) { ((FabHandleable) activity).hideFab(); } } private boolean stopScroll = false; private boolean addedUntilStopped = false; private void isAddedUntilStopped() { addedUntilStopped = tlLayoutManager.getChildCount() > 0 && tlLayoutManager.findFirstVisibleItemPosition() != 0; } public void stopScroll() { stopScroll = true; } public void startScroll() { stopScroll = false; } private boolean canScroll() { return isVisible() && !tlAdapter.isStatusViewSelected() && !stopScroll && !isScrolledByUser && !addedUntilStopped; } public void scrollToTop() { clearSelectedTweet(); binding.timeline.setLayoutFrozen(false); stopScroll = false; isScrolledByUser = false; addedUntilStopped = false; scrollTo(0); } private void scrollTo(int position) { // Log.d(TAG, "scrollTo: "); final int firstVisibleItemPosition = tlLayoutManager.findFirstVisibleItemPosition(); if (firstVisibleItemPosition - position < 4) { binding.timeline.smoothScrollToPosition(position); } else { binding.timeline.scrollToPosition(position + 3); binding.timeline.smoothScrollToPosition(position); } } public void clearSelectedTweet() { tlAdapter.clearSelectedEntity(); } public boolean isTweetSelected() { return tlAdapter.isStatusViewSelected(); } private OnUserIconClickedListener createUserIconClickedListener() { final FragmentActivity activity = getActivity(); if (activity instanceof OnUserIconClickedListener) { return ((OnUserIconClickedListener) activity); } else { return new OnUserIconClickedListener() { @Override public void onUserIconClicked(View view, User user) { // nop } }; } } public static final String EXTRA_PAGING = "paging"; private void fetchTweet(@Nullable Paging paging) { final FragmentActivity activity = getActivity(); if (activity instanceof OnFetchTweets) { fetchTweet((OnFetchTweets) activity, paging); return; } final Fragment targetFragment = getTargetFragment(); if (targetFragment != null) { final Intent intent = new Intent(); intent.putExtra(EXTRA_PAGING, paging); targetFragment.onActivityResult(getTargetRequestCode(), 1, intent); } } private void fetchTweet(@NonNull OnFetchTweets fetcher, @Nullable Paging paging) { if (paging == null) { fetcher.fetchTweet(); } else { fetcher.fetchTweet(paging); } } public static <T> TimelineFragment<T> getInstance(Fragment fragment, int requestCode) { final TimelineFragment<T> timelineFragment = new TimelineFragment<>(); timelineFragment.setTargetFragment(fragment, requestCode); return timelineFragment; } public void setSortedCache(SortedCache<T> sortedCache) { this.timelineStore = sortedCache; } @Nullable @SuppressWarnings("unused") public View getSelectedView() { return tlAdapter.getSelectedView(); } @Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { if (transit == FragmentTransaction.TRANSIT_FRAGMENT_OPEN) { if (!enter) { return AnimationUtils.loadAnimation(getContext(), android.R.anim.slide_out_right); } } if (transit == FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) { if (enter) { return AnimationUtils.loadAnimation(getContext(), android.R.anim.slide_in_left); } } return super.onCreateAnimation(transit, enter, nextAnim); } interface OnFetchTweets { void fetchTweet(); void fetchTweet(Paging paging); } }