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

Smoke and mirrors

rallat
August 10, 2016

Smoke and mirrors

magic tricks in Android UI development

rallat

August 10, 2016
Tweet

More Decks by rallat

Other Decks in Technology

Transcript

  1. /**
 * 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);
  2. • 2 RecyclerViews with two different adapters, same collection backing

    them up. • 1 GestureDetectors, 1 for scaling from small to medium recycler view. • 1 OnItemClickListener to do the transition from medium to full screen mode (can be another gesture listener too). • 1 OnItemTouchListener for both RecyclerViews and that will dispatch the TouchEvents to the GestureListener of the visible RecyclerView.
  3. 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;
 }
 
 }
 }

  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. ClipChildren=false • Allows child to be drawn outside is parent.

    • Spoiler alert, you need to add this to all the parents of the current child.
  7. ClipPadding=false • The animated view will be clipped by the

    padding of any parent ViewGroup • Only need to be set to false in the parents with padding.
  8. 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;
 }

  9. 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;
 }
  10. 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;
 }
 }
 }
  11. Other examples • Activity Transitions: shared element • RecyclerView, ListView:

    recycling views • Image fast loading: by preloading a blurry thumbnail