Java tutorial
/* * Copyright (c) 2015. Sociablue Labs * * 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.sociablue.nanodegree_p1; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.FragmentManager; import android.transition.ChangeBounds; import android.transition.ChangeTransform; import android.transition.Transition; import android.transition.TransitionSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.view.animation.TranslateAnimation; import android.widget.AdapterView; import android.widget.GridView; import android.widget.ImageView; import android.widget.RelativeLayout; import com.sociablue.nanodegree_p1.Constants.MdbConstants; import com.sociablue.nanodegree_p1.base.BaseFragment; import com.sociablue.nanodegree_p1.events.MenuItemClicked; import com.sociablue.nanodegree_p1.events.SharedImageLoadedEvent; import com.sociablue.nanodegree_p1.managers.EventManager; import com.sociablue.nanodegree_p1.menu.CustomRotatingMenu; import com.sociablue.nanodegree_p1.menu.MenuAdapter; import com.sociablue.nanodegree_p1.menu.MenuItem; import com.sociablue.nanodegree_p1.models.DiscoverMovieResponse; import com.sociablue.nanodegree_p1.models.Movie; import com.sociablue.nanodegree_p1.managers.NetworkManager; import com.sociablue.nanodegree_p1.utils.ReverseInterpolator; import com.sociablue.nanodegree_p1.utils.Utils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import retrofit.Callback; import retrofit.RetrofitError; import retrofit.client.Response; /** * Created by okikejiani on 2015-08-27. * List movies in a grid view */ public class MovieListFragment extends BaseFragment { MovieListAdapter mMovieListAdapter; GridView mMovieListGridView; List<Movie> mMovieList = new ArrayList<>(); View mClickedView; String mSort = MdbConstants.SORT_BY_POPULARITY_DESC; CustomRotatingMenu mMenu; ImageView moviePoster; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); loadMovieData(mSort); } /* * Adds Fab Click handler that toggles mMenu open and closed. Also Initializes FAB rotation animations */ private void initializeFab(View rootView) { final RelativeLayout buttonContainer = (RelativeLayout) rootView.findViewById(R.id.menu_button_container); final FloatingActionButton Fab = (FloatingActionButton) rootView.findViewById(R.id.fab); final ImageView bottomBar = (ImageView) rootView.findViewById(R.id.menu_bottom_bar); final ImageView topBar = (ImageView) rootView.findViewById(R.id.menu_top_bar); Fab.setOnClickListener(new View.OnClickListener() { @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void onClick(View v) { //Menu Button Icon Animation //Setting up necessary variables long animationDuration = 500; float containerHeight = buttonContainer.getHeight(); float containerCenterY = containerHeight / 2; float containerCenterX = buttonContainer.getWidth() / 2; float topBarCenter = topBar.getTop() + topBar.getHeight() / 2; float widthOfBar = topBar.getWidth(); float heightOfBar = topBar.getHeight(); final float distanceBetweenBars = (containerCenterY - topBarCenter); /** *TODO: Refactor block of code to use Value Property Animator. Should be more efficient to animate multiple properties *and objects at the same time. Also, will try to break intialization into smaller functions. */ //Setting up animations of hamburger bars and rotation /** * Animation For Top Menu Button Icon Bar. Sliding from the top to rest on top of the middle bar. * Y Translation is 1/2 the height of the hamburger bar minus the distance. * Subtracting the distance from the height because the distance between bars is * calculated of the exact center of the button. * With out the subtraction the bar would translate slightly below the middle bar. */ float yTranslation = heightOfBar / 2 - distanceBetweenBars; float xTranslation = widthOfBar / 2 + heightOfBar / 2; TranslateAnimation topBarTranslationAnim = new TranslateAnimation(Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, 0F, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, distanceBetweenBars); topBarTranslationAnim.setDuration((long) (animationDuration * 0.8)); topBarTranslationAnim.setFillAfter(true); //Animation for bottom hamburger bar. Translates and Rotates to create 'X' AnimationSet bottomBarAnimation = new AnimationSet(true); bottomBarAnimation.setFillAfter(true); //Rotate to create cross. (The cross becomes the X after the button rotation completes" RotateAnimation bottomBarRotationAnimation = new RotateAnimation(0f, 90f, Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 1f); bottomBarRotationAnimation.setDuration(animationDuration); bottomBarAnimation.addAnimation(bottomBarRotationAnimation); //Translate to correct X alignment TranslateAnimation bottomBarTranslationAnimation = new TranslateAnimation(Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, -xTranslation, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, -yTranslation); bottomBarTranslationAnimation.setDuration(animationDuration); bottomBarAnimation.addAnimation(bottomBarTranslationAnimation); //Button Specific Animations //Rotate Button Container RotateAnimation containerRotationAnimation = new RotateAnimation(0, 135f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); containerRotationAnimation.setDuration(animationDuration); containerRotationAnimation.setFillAfter(true); //Animate change of button color between Active and Disabled colors that have been //defined in color.xml int activeColor = getResources().getColor(R.color.active_button); int disabledColor = getResources().getColor(R.color.disabled_button); //Need to use ValueAnimator because property animator does not support BackgroundTint ValueAnimator buttonColorAnimation = ValueAnimator.ofArgb(activeColor, disabledColor); buttonColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Fab.setBackgroundTintList(ColorStateList.valueOf((int) animation.getAnimatedValue())); } }); buttonColorAnimation.setDuration(animationDuration); //Start all the animations topBar.startAnimation(topBarTranslationAnim); bottomBar.startAnimation(bottomBarAnimation); buttonContainer.startAnimation(containerRotationAnimation); buttonColorAnimation.start(); //Toogle mMenu open and closed if (mMenu.isOpen()) { //If mMenu is open, do the reverse of the animation containerRotationAnimation .setInterpolator(new ReverseInterpolator(new AccelerateInterpolator())); topBarTranslationAnim.setInterpolator(new ReverseInterpolator(new AccelerateInterpolator())); bottomBarAnimation.setInterpolator(new ReverseInterpolator(new AccelerateInterpolator())); buttonColorAnimation.setInterpolator(new ReverseInterpolator(new LinearInterpolator())); mMenu.close(); } else { bottomBarAnimation.setInterpolator(new AccelerateInterpolator()); mMenu.open(); } } }); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment final View rootView = inflater.inflate(R.layout.fragment_movie_list, container, false); RelativeLayout buttonContainer = (RelativeLayout) rootView.findViewById(R.id.menu_button_container); initializeFab(rootView); //Setup Menu mMenu = new CustomRotatingMenu(getActivity(), buttonContainer, (ViewGroup) rootView); MenuAdapter menuAdapter = new MenuAdapter(getActivity(), R.layout.menu_item); menuAdapter.addMenuItem( new MenuItem("Most Recent", R.drawable.ic_launcher, 1, MdbConstants.SORT_BY_RELEASE_DATE_DESC)); menuAdapter.addMenuItem( new MenuItem("Highest Rated", R.drawable.ic_launcher, 2, MdbConstants.SORT_BY_VOTE_AVERAGE_DESC)); menuAdapter.addMenuItem( new MenuItem("Most Popular", R.drawable.ic_launcher, 3, MdbConstants.SORT_BY_POPULARITY_DESC)); mMenu.bindAdapter(menuAdapter); ((ViewGroup) rootView).addView(mMenu, ((ViewGroup) rootView).indexOfChild(buttonContainer) - 1); mMovieListAdapter = new MovieListAdapter(getActivity(), mMovieList); mMovieListGridView = (GridView) rootView.findViewById(R.id.movielistgridview); configureGridView(rootView); //loadMovieData(mSort); return rootView; } /* * Calls network manager to get movie data and passes data to the Movie List Adapter */ private void loadMovieData(String sortBy) { //NetworkManager networkManager = ((MainActivity) getActivity()).getNetworkManager(); NetworkManager networkManager = new NetworkManager(); HashMap<String, Object> params = new HashMap<>(); params.put(MdbConstants.SORT_BY, sortBy); params.put(MdbConstants.VOTE_COUNT_GTE, MdbConstants.MIN_VOTE_COUNT); //params.put(MdbConstants.SORT_BY, MdbConstants.SORT_BY_RELEASE_DATE_DESC); // params.put(MdbConstants.SORT_BY, MdbConstants.SORT_BY_POPULARITY_DESC); // params.put(MdbConstants.SORT_BY, MdbConstants.SORT_BY_VOTE_AVERAGE_DESC); //params.put(MdbConstants.SORT_BY, MdbConstants.SORT_BY_REVENUE_DESC); networkManager.discoverMovies(params, new Callback<DiscoverMovieResponse>() { @Override public void success(DiscoverMovieResponse payload, Response response) { mMovieList = payload.getResults(); mMovieListAdapter.updateList(mMovieList); mMovieListAdapter.notifyDataSetChanged(); } @Override public void failure(RetrofitError error) { //TODO: Gracefully handle failures Utils.log(error.toString()); } }); } /* * Initializes gridview. binds adapter and adds click listener handler * Onclick Detail Pager Fragment is created and Exit Transition is started. */ private void configureGridView(final View rootView) { setGridColumns(); mMovieListGridView.setAdapter(mMovieListAdapter); mMovieListGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { MovieDetailPagerFragment moviePagerFragment = new MovieDetailPagerFragment(); moviePagerFragment.setMovieList(mMovieList); moviePagerFragment.setStartPosition(position); mClickedView = view; setupTransitions(moviePagerFragment, (ImageView) view); int[] location = new int[2]; view.getLocationInWindow(location); Rect positionRect = new Rect(); int statusBarHeight = Utils.screenHeight() - rootView.getMeasuredHeight(); view.getGlobalVisibleRect(positionRect); startFragmentExitTransition(view.getWidth(), view.getHeight(), location[0], location[1] - statusBarHeight, mMovieList.get(position)); } }); } /* * Adds interstitial view that covers the screen leaving only the click image in view. */ private void startFragmentExitTransition(int width, int height, int x, int y, Movie movie) { RelativeLayout rl = (RelativeLayout) getView().findViewById(R.id.movie_list_container); PosterImageView posterImageView = new PosterImageView(getActivity()); View transitionOverlay = new View(getActivity()); // RelativeLayout.LayoutParams overlayParams = // new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); RelativeLayout.LayoutParams overlayParams = new RelativeLayout.LayoutParams(Utils.screenWidth(), Utils.screenHeight()); transitionOverlay.setBackgroundColor(Color.BLACK); rl.addView(transitionOverlay, overlayParams); Utils.circularReveal(transitionOverlay, x + width / 2, y + height / 2); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height); params.leftMargin = x; params.topMargin = y; posterImageView.setImageResource(MdbConstants.IMAGE_BASE_URL + "w500" + movie.getPosterPath()); rl.addView(posterImageView, params); } @Override public void onStop() { EventManager.unregister(this); super.onStop(); } @Override public void onResume() { //If returning from detail view, remove detail view fragment if (getFragmentManager().findFragmentByTag("detail") != null) { getFragmentManager().popBackStack("transaction2", FragmentManager.POP_BACK_STACK_INCLUSIVE); } super.onResume(); } @Override public void onStart() { super.onStart(); EventManager.register(this); } @Override public void onConfigurationChanged(Configuration newConfig) { // TODO: set number of columns based on the screen size super.onConfigurationChanged(newConfig); setGridColumns(); } //Check device orientation and then set number of columns private void setGridColumns() { int currentOrientation = getResources().getConfiguration().orientation; if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) { mMovieListGridView.setNumColumns(4); } else { mMovieListGridView.setNumColumns(2); } } /* * Configures the sharedelement transitions to apply to the exiting and entering fragments */ public void setupTransitions(final MovieDetailPagerFragment newFragment, ImageView posterImageView) { //TODO: Shared Element Return Transition if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Inflate transitions to apply TransitionSet changeTransitionSet = new TransitionSet(); Transition changeTransform = new ChangeTransform(); changeTransitionSet.addTransition(changeTransform); changeTransitionSet.addTransition(new ChangeBounds()); changeTransitionSet.setDuration(200); changeTransitionSet.setInterpolator(new AccelerateDecelerateInterpolator()); this.setAllowEnterTransitionOverlap(true); this.setAllowReturnTransitionOverlap(true); // Setup enter transition on second fragment newFragment.setSharedElementEnterTransition(changeTransitionSet); newFragment.setAllowEnterTransitionOverlap(true); // Find the shared element (in Fragment A) moviePoster = posterImageView; moviePoster.setTransitionName("movie_poster"); // Add and hide details view pager fragment. Adding fragment, creates view and downloads images. getFragmentManager().beginTransaction().add(R.id.movie_list_fragment, newFragment, "detail") .addToBackStack("transaction2").hide(newFragment).commit(); } else { // Code to run on older devices //TODO: Handle older devices } } /* * Removes Movie List Fragment when SharedElement has been loaded in the Details Pager Interstitial View. */ public void onEvent(SharedImageLoadedEvent event) { //TODO: make sure the share element is the same element that this fragment is waiting for. //if(event.getSharedElement().getTag or TransitionName == ) getFragmentManager().beginTransaction() .setCustomAnimations(R.anim.abc_slide_in_bottom, R.anim.abc_popup_exit) .addSharedElement(moviePoster, "movie_poster") .show(getFragmentManager().findFragmentByTag("detail")).remove(this).addToBackStack("transaction") .commit(); } /* * Closes the menu and refreshes the grid view */ public void onEvent(MenuItemClicked event) { //mMenu.close(); FloatingActionButton fab = (FloatingActionButton) getView().findViewById(R.id.fab); //Close menu and trigger Fab animation fab.callOnClick(); //Scroll to top of grid view mMovieListGridView.setSelection(0); loadMovieData((String) event.getMenuItem().getTag()); } }