diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileAdapter.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileAdapter.java index 357876a..0535f55 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileAdapter.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileAdapter.java @@ -1,15 +1,22 @@ package nl.myhyvesbookplus.tagram; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import com.bumptech.glide.Glide; import com.firebase.ui.storage.images.FirebaseImageLoader; @@ -33,6 +40,7 @@ public class ProfileAdapter extends BaseAdapter { private TextView comment; private TextView nietSlechts; private ImageView photo; + private Animator mCurrentAnimator; ProfileAdapter(Context context, ArrayList data) { mContext = context; @@ -61,12 +69,19 @@ public class ProfileAdapter extends BaseAdapter { View newRowView = findViews(rowView); UriPost post = (UriPost) getItem(position); comment.setText(post.getComment()); - StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(post.getUri()); + final StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(post.getUri()); Glide.with(mContext) .using(new FirebaseImageLoader()) .load(ref) .into(photo); + photo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + zoomImageFromThumb(photo, ref); + } + }); + return newRowView; } @@ -76,4 +91,141 @@ public class ProfileAdapter extends BaseAdapter { photo = (ImageView) rowView.findViewById(R.id.timeline_image_profile); return rowView; } + + /** + * https://developer.android.com/training/animation/zoom.html + * "Zooms" in a thumbnail view by assigning the high resolution image to a hidden "zoomed-in" + * image view and animating its bounds to fit the entire activity content area. + * + * @param thumbView The thumbnail view to zoom in. + * @param imageRef The high-resolution version of the image represented by the thumbnail. + */ + private void zoomImageFromThumb(final View thumbView, StorageReference imageRef) { + // If there's an animation in progress, cancel it immediately and proceed with this one. + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Load the high-resolution "zoomed-in" image. + final ImageView hiddenView = (ImageView) ((MainActivity) mContext).findViewById(R.id.expanded_image_profile); + + Glide.with(mContext) + .using(new FirebaseImageLoader()) + .load(imageRef) + .into(hiddenView); + + // Calculate the starting and ending bounds for the zoomed-in image. This step + // involves lots of math. Yay, math. + final Rect startBounds = new Rect(); + final Rect finalBounds = new Rect(); + final Point globalOffset = new Point(); + + // The start bounds are the global visible rectangle of the thumbnail, and the + // final bounds are the global visible rectangle of the container view. Also + // set the container view's offset as the origin for the bounds, since that's + // the origin for the positioning animation properties (X, Y). + thumbView.getGlobalVisibleRect(startBounds); + ((MainActivity) mContext).findViewById(R.id.relative_layout_timeline_profile).getGlobalVisibleRect(finalBounds, globalOffset); + startBounds.offset(-globalOffset.x, -globalOffset.y); + finalBounds.offset(-globalOffset.x, -globalOffset.y); + + // Adjust the start bounds to be the same aspect ratio as the final bounds using the + // "center crop" technique. This prevents undesirable stretching during the animation. + // Also calculate the start scaling factor (the end scaling factor is always 1.0). + float startScale; + if ((float) finalBounds.width() / finalBounds.height() + > (float) startBounds.width() / startBounds.height()) { + // Extend start bounds horizontally + startScale = (float) startBounds.height() / finalBounds.height(); + float startWidth = startScale * finalBounds.width(); + float deltaWidth = (startWidth - startBounds.width()) / 2; + startBounds.left -= deltaWidth; + startBounds.right += deltaWidth; + } else { + // Extend start bounds vertically + startScale = (float) startBounds.width() / finalBounds.width(); + float startHeight = startScale * finalBounds.height(); + float deltaHeight = (startHeight - startBounds.height()) / 2; + startBounds.top -= deltaHeight; + startBounds.bottom += deltaHeight; + } + + // Hide the thumbnail and show the zoomed-in view. When the animation begins, + // it will position the zoomed-in view in the place of the thumbnail. + thumbView.setAlpha(0f); + hiddenView.setVisibility(View.VISIBLE); + + // Set the pivot point for SCALE_X and SCALE_Y transformations to the top-left corner of + // the zoomed-in view (the default is the center of the view). + hiddenView.setPivotX(0f); + hiddenView.setPivotY(0f); + + // Construct and run the parallel animation of the four translation and scale properties + // (X, Y, SCALE_X, and SCALE_Y). + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(hiddenView, View.X, startBounds.left, + finalBounds.left)) + .with(ObjectAnimator.ofFloat(hiddenView, View.Y, startBounds.top, + finalBounds.top)) + .with(ObjectAnimator.ofFloat(hiddenView, View.SCALE_X, startScale, 1f)) + .with(ObjectAnimator.ofFloat(hiddenView, View.SCALE_Y, startScale, 1f)); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + + // Upon clicking the zoomed-in image, it should zoom back down to the original bounds + // and show the thumbnail instead of the expanded image. + final float startScaleFinal = startScale; + hiddenView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Animate the four positioning/sizing properties in parallel, back to their + // original values. + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(hiddenView, View.X, startBounds.left)) + .with(ObjectAnimator.ofFloat(hiddenView, View.Y, startBounds.top)) + .with(ObjectAnimator + .ofFloat(hiddenView, View.SCALE_X, startScaleFinal)) + .with(ObjectAnimator + .ofFloat(hiddenView, View.SCALE_Y, startScaleFinal)); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + thumbView.setAlpha(1f); + hiddenView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + thumbView.setAlpha(1f); + hiddenView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + } + }); + } } diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimeLineAdapter.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimeLineAdapter.java index 8282574..fb8c7ef 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimeLineAdapter.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimeLineAdapter.java @@ -1,16 +1,23 @@ package nl.myhyvesbookplus.tagram; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; import android.support.annotation.NonNull; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import com.bumptech.glide.Glide; import com.firebase.ui.storage.images.FirebaseImageLoader; @@ -35,6 +42,7 @@ public class TimeLineAdapter extends BaseAdapter implements AdapterView.OnItemCl private Context mContext; private ArrayList mData; private DatabaseReference mRef; + private Animator mCurrentAnimator; TimeLineAdapter(Context context, ArrayList data) { mContext = context; @@ -66,7 +74,7 @@ public class TimeLineAdapter extends BaseAdapter implements AdapterView.OnItemCl TextView comment = (TextView) rowView.findViewById(R.id.comment_timeline); final TextView nietSlechts = (TextView) rowView.findViewById(R.id.niet_slecht_count); TextView dateTime = (TextView) rowView.findViewById(R.id.timeline_date); - ImageView photo = (ImageView) rowView.findViewById(R.id.timeline_image); + final ImageView photo = (ImageView) rowView.findViewById(R.id.timeline_image); final ImageButton nietSlechtButton = (ImageButton) rowView.findViewById(R.id.niet_slecht_button); final UriPost post = (UriPost) getItem(position); @@ -91,12 +99,18 @@ public class TimeLineAdapter extends BaseAdapter implements AdapterView.OnItemCl dateTime.setText(post.getDate().toString()); - StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(post.getUri()); + final StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(post.getUri()); Glide.with(mContext) .using(new FirebaseImageLoader()) .load(ref) .into(photo); + photo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + zoomImageFromThumb(photo, ref); + } + }); return rowView; } @@ -119,4 +133,140 @@ public class TimeLineAdapter extends BaseAdapter implements AdapterView.OnItemCl Log.d(TAG, "onItemClick: rowNumber! "+ position); } + /** + * https://developer.android.com/training/animation/zoom.html + * "Zooms" in a thumbnail view by assigning the high resolution image to a hidden "zoomed-in" + * image view and animating its bounds to fit the entire activity content area. + * + * @param thumbView The thumbnail view to zoom in. + * @param imageRef The high-resolution version of the image represented by the thumbnail. + */ + private void zoomImageFromThumb(final View thumbView, StorageReference imageRef) { + // If there's an animation in progress, cancel it immediately and proceed with this one. + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Load the high-resolution "zoomed-in" image. + final ImageView hiddenView = (ImageView) ((MainActivity) mContext).findViewById(R.id.expanded_image); + + Glide.with(mContext) + .using(new FirebaseImageLoader()) + .load(imageRef) + .into(hiddenView); + + // Calculate the starting and ending bounds for the zoomed-in image. This step + // involves lots of math. Yay, math. + final Rect startBounds = new Rect(); + final Rect finalBounds = new Rect(); + final Point globalOffset = new Point(); + + // The start bounds are the global visible rectangle of the thumbnail, and the + // final bounds are the global visible rectangle of the container view. Also + // set the container view's offset as the origin for the bounds, since that's + // the origin for the positioning animation properties (X, Y). + thumbView.getGlobalVisibleRect(startBounds); + ((MainActivity) mContext).findViewById(R.id.relative_layout_timeline).getGlobalVisibleRect(finalBounds, globalOffset); + startBounds.offset(-globalOffset.x, -globalOffset.y); + finalBounds.offset(-globalOffset.x, -globalOffset.y); + + // Adjust the start bounds to be the same aspect ratio as the final bounds using the + // "center crop" technique. This prevents undesirable stretching during the animation. + // Also calculate the start scaling factor (the end scaling factor is always 1.0). + float startScale; + if ((float) finalBounds.width() / finalBounds.height() + > (float) startBounds.width() / startBounds.height()) { + // Extend start bounds horizontally + startScale = (float) startBounds.height() / finalBounds.height(); + float startWidth = startScale * finalBounds.width(); + float deltaWidth = (startWidth - startBounds.width()) / 2; + startBounds.left -= deltaWidth; + startBounds.right += deltaWidth; + } else { + // Extend start bounds vertically + startScale = (float) startBounds.width() / finalBounds.width(); + float startHeight = startScale * finalBounds.height(); + float deltaHeight = (startHeight - startBounds.height()) / 2; + startBounds.top -= deltaHeight; + startBounds.bottom += deltaHeight; + } + + // Hide the thumbnail and show the zoomed-in view. When the animation begins, + // it will position the zoomed-in view in the place of the thumbnail. + thumbView.setAlpha(0f); + hiddenView.setVisibility(View.VISIBLE); + + // Set the pivot point for SCALE_X and SCALE_Y transformations to the top-left corner of + // the zoomed-in view (the default is the center of the view). + hiddenView.setPivotX(0f); + hiddenView.setPivotY(0f); + + // Construct and run the parallel animation of the four translation and scale properties + // (X, Y, SCALE_X, and SCALE_Y). + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(hiddenView, View.X, startBounds.left, + finalBounds.left)) + .with(ObjectAnimator.ofFloat(hiddenView, View.Y, startBounds.top, + finalBounds.top)) + .with(ObjectAnimator.ofFloat(hiddenView, View.SCALE_X, startScale, 1f)) + .with(ObjectAnimator.ofFloat(hiddenView, View.SCALE_Y, startScale, 1f)); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + + // Upon clicking the zoomed-in image, it should zoom back down to the original bounds + // and show the thumbnail instead of the expanded image. + final float startScaleFinal = startScale; + hiddenView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Animate the four positioning/sizing properties in parallel, back to their + // original values. + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(hiddenView, View.X, startBounds.left)) + .with(ObjectAnimator.ofFloat(hiddenView, View.Y, startBounds.top)) + .with(ObjectAnimator + .ofFloat(hiddenView, View.SCALE_X, startScaleFinal)) + .with(ObjectAnimator + .ofFloat(hiddenView, View.SCALE_Y, startScaleFinal)); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + thumbView.setAlpha(1f); + hiddenView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + thumbView.setAlpha(1f); + hiddenView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + } + }); + } } diff --git a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_profile_timeline.xml b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_profile_timeline.xml index fd06e94..56a00ad 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_profile_timeline.xml +++ b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_profile_timeline.xml @@ -1,4 +1,5 @@ - - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_timeline.xml b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_timeline.xml index fe7a080..effb17f 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_timeline.xml +++ b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_timeline.xml @@ -1,4 +1,5 @@ - + - \ No newline at end of file + \ No newline at end of file