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

Droidkaigi Smoke and Mirrors

rallat
March 10, 2017
79

Droidkaigi Smoke and Mirrors

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