Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Droidkaigi Smoke and Mirrors

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for rallat rallat
March 10, 2017
110

Droidkaigi Smoke and Mirrors

Avatar for rallat

rallat

March 10, 2017
Tweet

Transcript

  1. /**
 * A transition listener receives notifications from a transition.


    * Notifications indicate transition lifecycle events.
 */
 public static interface TransitionListener {
 
 void onTransitionStart(Transition transition);
 
 void onTransitionEnd(Transition transition);
 
 void onTransitionCancel(Transition transition);
 
 void onTransitionPause(Transition transition);
 
 void onTransitionResume(Transition transition);
 }

  2. private View.OnTouchListener touchEater = new View.OnTouchListener() {
 @Override
 public boolean

    onTouch(View view, MotionEvent motionEvent) {
 return true;
 }
 };
  3. <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout
 android:id="@+id/parent"
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:padding="@dimen/activity_vertical_margin">
 <FrameLayout


    android:layout_width="300dp"
 android:layout_height="300dp"
 android:background="@color/colorAccent"
 android:clipChildren="false">
 <FrameLayout
 android:layout_width="200dp"
 android:layout_height="200dp"
 android:background="@color/colorPrimary">
 <ImageView
 android:id="@+id/imageview"
 android:layout_width="100dp"
 android:layout_height="100dp"
 android:src="@drawable/profile"/>
 </FrameLayout>
 </FrameLayout>
 </FrameLayout>

  4. <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout
 android:id="@+id/parent"
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:padding="@dimen/activity_vertical_margin">
 <FrameLayout


    android:layout_width="300dp"
 android:layout_height="300dp"
 android:background="@color/colorAccent"
 android:clipChildren="false">
 <FrameLayout
 android:layout_width="200dp"
 android:layout_height="200dp"
 android:background="@color/colorPrimary">
 <ImageView
 android:id="@+id/imageview"
 android:layout_width="100dp"
 android:layout_height="100dp"
 android:src="@drawable/profile"/>
 </FrameLayout>
 </FrameLayout>
 </FrameLayout>

  5. <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout
 android:id="@+id/parent"
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:clipChildren=“false"
 android:padding="@dimen/activity_vertical_margin">


    <FrameLayout
 android:layout_width="300dp"
 android:layout_height="300dp"
 android:background="@color/colorAccent"
 android:clipChildren="false">
 <FrameLayout
 android:layout_width="200dp"
 android:layout_height="200dp"
 android:background="@color/colorPrimary">
 <ImageView
 android:id="@+id/imageview"
 android:layout_width="100dp"
 android:layout_height="100dp"
 android:src="@drawable/profile"/>
 </FrameLayout>
 </FrameLayout>
 </FrameLayout>
  6. <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout
 android:id="@+id/parent"
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:clipChildren=“false"
 android:padding="@dimen/activity_vertical_margin">


    <FrameLayout
 android:layout_width="300dp"
 android:layout_height="300dp"
 android:background="@color/colorAccent"
 android:clipChildren="false">
 <FrameLayout
 android:layout_width="200dp"
 android:layout_height="200dp"
 android:background="@color/colorPrimary">
 <ImageView
 android:id="@+id/imageview"
 android:layout_width="100dp"
 android:layout_height="100dp"
 android:src="@drawable/profile"/>
 </FrameLayout>
 </FrameLayout>
 </FrameLayout>
  7. Utils public static void disableParentsClip(@NonNull View view) {
 while (view.getParent()

    != null && view.getParent() instanceof ViewGroup) {
 ViewGroup viewGroup = (ViewGroup) view.getParent();
 viewGroup.setClipChildren(false);
 viewGroup.setClipToPadding(false);
 view = viewGroup;
 }
 }
 
 public static void enableParentsClip(@NonNull View view) {
 while (view.getParent() != null && view.getParent() instanceof ViewGroup) {
 ViewGroup viewGroup = (ViewGroup) view.getParent();
 viewGroup.setClipChildren(true);
 viewGroup.setClipToPadding(true);
 view = viewGroup;
 }

  8. /**
 * Sets the x location of the point around

    which the view * is {@link #setRotation(float) rotated} and * {@link #setScaleX(float) scaled}.
 * By default, the pivot point is centered on the object. */ smallRecyclerView.setPivotX(0); smallRecyclerView.setPivotY(0); mediumRecyclerView.setPivotX(0);
 mediumRecyclerView.setPivotY(0);
  9. /**
 * Sets the x location of the point around

    which the view * is {@link #setRotation(float) rotated} and * {@link #setScaleX(float) scaled}.
 * By default, the pivot point is centered on the object. */ smallRecyclerView.setPivotX(0); smallRecyclerView.setPivotY(0); mediumRecyclerView.setPivotX(0);
 mediumRecyclerView.setPivotY(0);
  10. public class ItemTouchListenerDispatcher implements RecyclerView.OnItemTouchListener {
 …
 @Override
 public void

    onTouchEvent(RecyclerView rv, MotionEvent e) {
 currentSpan = getSpan(e);
 switch (rv.getId()) {
 case R.id.mediumRecyclerView: {
 if (currentSpan < 0) {
 galleryGestureDetector.onTouchEvent(e);
 } else if (currentSpan == 0) {
 final View childViewUnder = rv.findChildViewUnder(e.getX(), e.getY());
 if (childViewUnder != null) {
 childViewUnder.performClick();
 }
 }
 break;
 }
 case R.id.smallRecyclerView: {
 galleryGestureDetector.onTouchEvent(e);
 break;
 }
 default: {
 break;
 }
 
 }
 }
 …
  11. public class ItemTouchListenerDispatcher implements RecyclerView.OnItemTouchListener {
 …
 @Override
 public void

    onTouchEvent(RecyclerView rv, MotionEvent e) {
 currentSpan = getSpan(e);
 switch (rv.getId()) {
 case R.id.mediumRecyclerView: {
 if (currentSpan < 0) {
 galleryGestureDetector.onTouchEvent(e);
 } else if (currentSpan == 0) {
 final View childViewUnder = rv.findChildViewUnder(e.getX(), e.getY());
 if (childViewUnder != null) {
 childViewUnder.performClick();
 }
 }
 break;
 }
 case R.id.smallRecyclerView: {
 galleryGestureDetector.onTouchEvent(e);
 break;
 }
 default: {
 break;
 }
 
 }
 }
 …
  12. public class ItemTouchListenerDispatcher implements RecyclerView.OnItemTouchListener {
 …
 @Override
 public void

    onTouchEvent(RecyclerView rv, MotionEvent e) {
 currentSpan = getSpan(e);
 switch (rv.getId()) {
 case R.id.mediumRecyclerView: {
 if (currentSpan < 0) {
 galleryGestureDetector.onTouchEvent(e);
 } else if (currentSpan == 0) {
 final View childViewUnder = rv.findChildViewUnder(e.getX(), e.getY());
 if (childViewUnder != null) {
 childViewUnder.performClick();
 }
 }
 break;
 }
 case R.id.smallRecyclerView: {
 galleryGestureDetector.onTouchEvent(e);
 break;
 }
 default: {
 break;
 }
 
 }
 }
 …
  13. public class ItemTouchListenerDispatcher implements RecyclerView.OnItemTouchListener {
 …
 @Override
 public void

    onTouchEvent(RecyclerView rv, MotionEvent e) {
 currentSpan = getSpan(e);
 switch (rv.getId()) {
 case R.id.mediumRecyclerView: {
 if (currentSpan < 0) {
 galleryGestureDetector.onTouchEvent(e);
 } else if (currentSpan == 0) {
 final View childViewUnder = rv.findChildViewUnder(e.getX(), e.getY());
 if (childViewUnder != null) {
 childViewUnder.performClick();
 }
 }
 break;
 }
 case R.id.smallRecyclerView: {
 galleryGestureDetector.onTouchEvent(e);
 break;
 }
 default: {
 break;
 }
 
 }
 }
 …
  14. public interface OnScaleGestureListener {
 /**
 * Responds to scaling events

    for a gesture in progress.
 * Reported by pointer motion.
 */
 public boolean onScale(ScaleGestureDetector detector);
 
 /**
 * Responds to the beginning of a scaling gesture. Reported by
 * new pointers going down.
 */
 public boolean onScaleBegin(ScaleGestureDetector detector);
 
 /**
 * Responds to the end of a scale gesture. Reported by existing
 * pointers going up.
 
 * @param detector The detector reporting the event - use this to
 * retrieve extended info about event state.
 */
 public void onScaleEnd(ScaleGestureDetector detector);
 }
  15. During Scale @Override
 public boolean onScale(@NonNull ScaleGestureDetector detector) {
 if

    (gestureTolerance(detector)) {
 //small
 scaleFactor *= detector.getScaleFactor();
 scaleFactor = Math.max(1f, Math.min(scaleFactor, SMALL_MAX_SCALE_FACTOR));
 isInProgress = scaleFactor > 1;
 smallRecyclerView.setScaleX(scaleFactor);
 smallRecyclerView.setScaleY(scaleFactor);
 
 //medium
 scaleFactorMedium *= detector.getScaleFactor();
 scaleFactorMedium = Math.max(0.8f, Math.min(scaleFactorMedium, 1f));
 mediumRecyclerView.setScaleX(scaleFactorMedium);
 mediumRecyclerView.setScaleY(scaleFactorMedium);
 
 //alpha
 mediumRecyclerView.setAlpha((scaleFactor - 1) / (0.25f));
 smallRecyclerView.setAlpha(1 - (scaleFactor - 1) / (0.25f));
 
 }
 return true;
 }
  16. During Scale @Override
 public boolean onScale(@NonNull ScaleGestureDetector detector) {
 if

    (gestureTolerance(detector)) {
 //small
 scaleFactor *= detector.getScaleFactor();
 scaleFactor = Math.max(1f, Math.min(scaleFactor, SMALL_MAX_SCALE_FACTOR));
 isInProgress = scaleFactor > 1;
 smallRecyclerView.setScaleX(scaleFactor);
 smallRecyclerView.setScaleY(scaleFactor);
 
 //medium
 scaleFactorMedium *= detector.getScaleFactor();
 scaleFactorMedium = Math.max(0.8f, Math.min(scaleFactorMedium, 1f));
 mediumRecyclerView.setScaleX(scaleFactorMedium);
 mediumRecyclerView.setScaleY(scaleFactorMedium);
 
 //alpha
 mediumRecyclerView.setAlpha((scaleFactor - 1) / (0.25f));
 smallRecyclerView.setAlpha(1 - (scaleFactor - 1) / (0.25f));
 
 }
 return true;
 }
  17. During Scale @Override
 public boolean onScale(@NonNull ScaleGestureDetector detector) {
 if

    (gestureTolerance(detector)) {
 //small
 scaleFactor *= detector.getScaleFactor();
 scaleFactor = Math.max(1f, Math.min(scaleFactor, 1.25f));
 isInProgress = scaleFactor > 1;
 smallRecyclerView.setScaleX(scaleFactor);
 smallRecyclerView.setScaleY(scaleFactor);
 
 //medium
 scaleFactorMedium *= detector.getScaleFactor();
 scaleFactorMedium = Math.max(0.8f, Math.min(scaleFactorMedium, 1f));
 mediumRecyclerView.setScaleX(scaleFactorMedium);
 mediumRecyclerView.setScaleY(scaleFactorMedium);
 
 //alpha
 mediumRecyclerView.setAlpha((scaleFactor - 1) / (0.25f));
 smallRecyclerView.setAlpha(1 - (scaleFactor - 1) / (0.25f));
 
 }
 return true;
 }
  18. During Scale @Override
 public boolean onScale(@NonNull ScaleGestureDetector detector) {
 if

    (gestureTolerance(detector)) {
 //small
 scaleFactor *= detector.getScaleFactor();
 scaleFactor = Math.max(1f, Math.min(scaleFactor, SMALL_MAX_SCALE_FACTOR));
 isInProgress = scaleFactor > 1;
 smallRecyclerView.setScaleX(scaleFactor);
 smallRecyclerView.setScaleY(scaleFactor);
 
 //medium
 scaleFactorMedium *= detector.getScaleFactor();
 scaleFactorMedium = Math.max(0.8f, Math.min(scaleFactorMedium, 1f));
 mediumRecyclerView.setScaleX(scaleFactorMedium);
 mediumRecyclerView.setScaleY(scaleFactorMedium);
 
 //alpha
 mediumRecyclerView.setAlpha((scaleFactor - 1) / (0.25f));
 smallRecyclerView.setAlpha(1 - (scaleFactor - 1) / (0.25f));
 
 }
 return true;
 }
  19. Scale ends @Override
 public void onScaleEnd(@NonNull ScaleGestureDetector detector) {
 if

    (IsScaleInProgress()) {
 if (scaleFactor < TRANSITION_BOUNDARY) {
 transitionFromMediumToSmall();
 scaleFactor = 0;
 scaleFactorMedium = 0;
 } else {
 transitionFromSmallToMedium();
 scaleFactor = SMALL_MAX_SCALE_FACTOR;
 scaleFactorMedium = 1f;
 }
 }
 }
  20. Scale ends @Override
 public void onScaleEnd(@NonNull ScaleGestureDetector detector) {
 if

    (IsScaleInProgress()) {
 if (scaleFactor < TRANSITION_BOUNDARY) {
 transitionFromMediumToSmall();
 scaleFactor = 0;
 scaleFactorMedium = 0;
 } else {
 transitionFromSmallToMedium();
 scaleFactor = SMALL_MAX_SCALE_FACTOR;
 scaleFactorMedium = 1f;
 }
 }
 }
  21. Scale ends @Override
 public void onScaleEnd(@NonNull ScaleGestureDetector detector) {
 if

    (IsScaleInProgress()) {
 if (scaleFactor < TRANSITION_BOUNDARY) {
 transitionFromMediumToSmall();
 scaleFactor = 0;
 scaleFactorMedium = 0;
 } else {
 transitionFromSmallToMedium();
 scaleFactor = SMALL_MAX_SCALE_FACTOR;
 scaleFactorMedium = 1f;
 }
 }
 }
  22. Scale ends @Override
 public void onScaleEnd(@NonNull ScaleGestureDetector detector) {
 if

    (IsScaleInProgress()) {
 if (scaleFactor < TRANSITION_BOUNDARY) {
 transitionFromMediumToSmall();
 scaleFactor = 0;
 scaleFactorMedium = 0;
 } else {
 transitionFromSmallToMedium();
 scaleFactor = SMALL_MAX_SCALE_FACTOR;
 scaleFactorMedium = 1f;
 }
 }
 }
  23. Transition to full screen 
 @Override
 public void onClick(@NonNull final

    View itemView) {
 ViewGroupOverlay overlay = fullScreenContainer.getOverlay();
 overlay.clear();
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 fullScreenContainer.setVisibility(View.VISIBLE);
 itemView.animate() .x(DELTA_TO_CENTER_X).y(DELTA_TO_CENTER_Y)
 .scaleX(DELTA_SCALE).scaleY(DELTA_SCALE) .withEndAction(setTransitionToRecyclerView()).start();
 }
 }
  24. Transition to full screen 
 @Override
 public void onClick(@NonNull final

    View itemView) {
 ViewGroupOverlay overlay = fullScreenContainer.getOverlay();
 overlay.clear();
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 fullScreenContainer.setVisibility(View.VISIBLE);
 itemView.animate() .x(DELTA_TO_CENTER_X).y(DELTA_TO_CENTER_Y)
 .scaleX(DELTA_SCALE).scaleY(DELTA_SCALE) .withEndAction(setTransitionToRecyclerView()).start();
 }
 }
  25. Transition to full screen 
 @Override
 public void onClick(@NonNull final

    View itemView) {
 ViewGroupOverlay overlay = fullScreenContainer.getOverlay();
 overlay.clear();
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 fullScreenContainer.setVisibility(View.VISIBLE);
 itemView.animate() .x(DELTA_TO_CENTER_X).y(DELTA_TO_CENTER_Y)
 .scaleX(DELTA_SCALE).scaleY(DELTA_SCALE) .withEndAction(setTransitionToRecyclerView()).start();
 }
 }
  26. Transition to full screen 
 @Override
 public void onClick(@NonNull final

    View itemView) {
 ViewGroupOverlay overlay = fullScreenContainer.getOverlay();
 overlay.clear();
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 fullScreenContainer.setVisibility(View.VISIBLE);
 itemView.animate() .x(DELTA_TO_CENTER_X).y(DELTA_TO_CENTER_Y)
 .scaleX(DELTA_SCALE).scaleY(DELTA_SCALE) .withEndAction(setTransitionToRecyclerView()).start();
 }
 }
  27. Transition to full screen 
 @Override
 public void onClick(@NonNull final

    View itemView) {
 ViewGroupOverlay overlay = fullScreenContainer.getOverlay();
 overlay.clear();
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 fullScreenContainer.setVisibility(View.VISIBLE);
 itemView.animate() .x(DELTA_TO_CENTER_X).y(DELTA_TO_CENTER_Y)
 .scaleX(DELTA_SCALE).scaleY(DELTA_SCALE) .withEndAction(setTransitionToRecyclerView()).start();
 }
 }
  28. Transition to full screen private Runnable setTransitionToRecyclerView() {
 return new

    Runnable() {
 @Override
 public void run() {
 fullScreenContainer.setBackgroundColor(BLACK);
 fullScreenContainer.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 itemView.animate().x(originX).y(originY) .scaleY(1).scaleX(1).withEndAction(
 new Runnable() {
 @Override
 public void run() {
 overlay.remove(itemView);
 fullScreenContainer.setVisibility(View.GONE);
 }
 }).start();
 }
 });
 }
 };
 }
  29. Transition to full screen private Runnable setTransitionToRecyclerView() {
 return new

    Runnable() {
 @Override
 public void run() {
 fullScreenContainer.setBackgroundColor(BLACK);
 fullScreenContainer.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 itemView.animate().x(originX).y(originY) .scaleY(1).scaleX(1).withEndAction(
 new Runnable() {
 @Override
 public void run() {
 overlay.remove(itemView);
 fullScreenContainer.setVisibility(View.GONE);
 }
 }).start();
 }
 });
 }
 };
 }
  30. Transition to full screen private Runnable setTransitionToRecyclerView() {
 return new

    Runnable() {
 @Override
 public void run() {
 fullScreenContainer.setBackgroundColor(BLACK);
 fullScreenContainer.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 itemView.animate().x(originX).y(originY) .scaleY(1).scaleX(1).withEndAction(
 new Runnable() {
 @Override
 public void run() {
 overlay.remove(itemView);
 fullScreenContainer.setVisibility(View.GONE);
 }
 }).start();
 }
 });
 }
 };
 }
  31. Transition to full screen private Runnable setTransitionToRecyclerView() {
 return new

    Runnable() {
 @Override
 public void run() {
 fullScreenContainer.setBackgroundColor(BLACK);
 fullScreenContainer.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 overlay.add(itemView);
 fullScreenContainer.setBackgroundColor(TRANSPARENT);
 itemView.animate().x(originX).y(originY) .scaleY(1).scaleX(1).withEndAction(
 new Runnable() {
 @Override
 public void run() {
 overlay.remove(itemView);
 fullScreenContainer.setVisibility(View.GONE);
 }
 }).start();
 }
 });
 }
 };
 }
  32. Magic tricks • ViewOverlay for animations • Single Activity allows

    control of the transition by the user touch • ClipPadding, ClipChildren to draw over parents&paddings
  33. Magic tricks • ViewOverlay for animations • Single Activity allows

    control of the transition by the user touch • Fast animations to hide implementation details and make it look cool • ClipPadding, ClipChildren to draw over parents&paddings