Niedzielski has uploaded a new change for review. https://gerrit.wikimedia.org/r/253065
Change subject: Hygiene: move lead parallax to Drawable matrix ...................................................................... Hygiene: move lead parallax to Drawable matrix The parallax effect used by the lead image was accomplished by sizing its housing ImageView to the full dimensions of the image and then translating the View itself to achieve a parallax effect. This was nice because it avoided math and translations are cheap but poor because it changed layout parameters without concern for adjacent Views. This unusual behavior made it difficult to visualize the View hierarchy. This patch moves the parallax effect from raw View resizing and translations to the ImageView's Drawable matrix. The math is a little intimidating in some parts, but it's mostly pure (free of implementation details), and the View is now boring and predictable. This change will make it easy to add or remove Views without surprising things happening. Bug: T116122 Change-Id: I17ae6c24f876a37b1c86180daf4794dfaa2696bd --- M app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java M app/src/main/java/org/wikipedia/views/ArticleHeaderView.java M app/src/main/res/layout/view_article_header.xml 3 files changed, 92 insertions(+), 53 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia refs/changes/65/253065/1 diff --git a/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java b/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java index dcfbb5e..dc4c222 100755 --- a/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java +++ b/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java @@ -13,7 +13,6 @@ import android.graphics.PointF; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.FrameLayout; import android.widget.ImageView; import org.json.JSONException; @@ -337,10 +336,6 @@ displayHeightDp = (int) (displayHeightPx / displayDensity); } - private void setImageLayoutParams(int width, int height) { - image.setLayoutParams(new FrameLayout.LayoutParams(width, height)); - } - private void loadLeadImage() { loadLeadImage(getLeadImageUrl()); } @@ -352,7 +347,7 @@ private void loadLeadImage(@Nullable String url) { if (!isMainPage() && !TextUtils.isEmpty(url) && leadImagesEnabled) { String fullUrl = WikipediaApp.getInstance().getNetworkProtocol() + ":" + url; - articleHeaderView.setImageYOffset(0); + articleHeaderView.setImageYScalar(0); articleHeaderView.loadImage(fullUrl); } } @@ -444,51 +439,36 @@ } private void detectFace(int bmpHeight, float aspect, @Nullable PointF faceLocation) { - int newWidth = image.getWidth(); - int newHeight = (int) (newWidth * aspect); + faceYOffsetNormalized = faceYScalar(bmpHeight, faceLocation); - // give our image an offset based on the location of the face, - // relative to the image container - float scale = (float) newHeight / (float) bmpHeight; - int imageBaseYOffset; - if (faceLocation != null) { - int faceY = (int) (faceLocation.y * scale); - // if we have a face, then offset to the face location - imageBaseYOffset = -(faceY - (imagePlaceholder.getHeight() / 2)); - // Adjust the face position by a slight amount. - // The face recognizer gives the location of the *eyes*, whereas we actually - // want to center on the *nose*... - imageBaseYOffset += getDimension(R.dimen.face_detection_nose_y_offset); - faceYOffsetNormalized = faceLocation.y / bmpHeight; - } else { - // No face, so we'll just chop the top 25% off rather than centering - final float oneQuarter = 0.25f; - imageBaseYOffset = -(int) ((newHeight - imagePlaceholder.getHeight()) * oneQuarter); - faceYOffsetNormalized = oneQuarter; - } + float scalar = constrainScalar(faceYOffsetNormalized); - // is the offset too far to the top? - imageBaseYOffset = Math.min(0, imageBaseYOffset); - - // is the offset too far to the bottom? - imageBaseYOffset = Math.max(imageBaseYOffset, imagePlaceholder.getHeight() - newHeight); - - // resize our image to have the same proportions as the acquired bitmap - if (newHeight < imagePlaceholder.getHeight()) { - // if the height of the image is less than the container, then just - // make it the same height as the placeholder. - setImageLayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, imagePlaceholder.getHeight()); - imageBaseYOffset = 0; - } else { - setImageLayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, newHeight); - } - - articleHeaderView.setImageYOffset(imageBaseYOffset); + articleHeaderView.setImageYScalar(scalar); // fade in the new image! ViewAnimations.crossFade(imagePlaceholder, image); startKenBurnsAnimation(); } + + private float constrainScalar(float scalar) { + scalar = Math.max(0, scalar); + scalar = Math.min(scalar, 1); + return scalar; + } + + private float faceYScalar(int bmpHeight, PointF faceLocation) { + final float defaultOffsetScalar = .25f; + float scalar = defaultOffsetScalar; + if (faceLocation != null) { + scalar = faceLocation.y / bmpHeight; + // TODO: if it is desirable to offset to the nose, replace this arbitrary hardcoded + // value with a proportion. FaceDetector.eyesDistance() presumably provides + // the interpupillary distance in pixels. We can multiply this measurement by + // the proportion of the length of the nose to the IPD. + scalar += getDimension(R.dimen.face_detection_nose_y_offset) / bmpHeight; + } + return scalar; + } } } diff --git a/app/src/main/java/org/wikipedia/views/ArticleHeaderView.java b/app/src/main/java/org/wikipedia/views/ArticleHeaderView.java index 9dc48eb..beb5f9a 100644 --- a/app/src/main/java/org/wikipedia/views/ArticleHeaderView.java +++ b/app/src/main/java/org/wikipedia/views/ArticleHeaderView.java @@ -5,6 +5,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; @@ -58,7 +59,7 @@ @NonNull private CharSequence subtitle = ""; @Nullable private String pronunciationUrl; - private int imageYOffset; + private float imageYScalar; @NonNull private final AvPlayer avPlayer = new DefaultAvPlayer(new MediaPlayerImplementation()); @@ -148,8 +149,8 @@ return returnedBitmap; } - public void setImageYOffset(int offset) { - imageYOffset = offset; + public void setImageYScalar(float offset) { + imageYScalar = offset; updateParallaxScroll(); } @@ -217,6 +218,12 @@ } @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updateParallaxScroll(); + } + + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); avPlayer.deinit(); @@ -229,7 +236,18 @@ private void updateParallaxScroll(int scrollY) { int offset = Math.min(getHeight(), scrollY); setTranslationY(-offset); - image.setTranslationY(imageYOffset + offset / 2); + + ImageView view; + float scalar; + if (image.getDrawable() == null) { + view = placeholder; + scalar = 0; + } else { + view = image; + scalar = imageYScalar; + } + + updateImageViewParallax(view, scalar, offset / 2); } private void updateText() { @@ -250,6 +268,47 @@ text.setText(builder); } + private void updateImageViewParallax(ImageView view, float scalar, int offset) { + Matrix matrix = centerCropWithOffsetScalar(view, view.getDrawable(), view.getImageMatrix(), scalar); + matrix.postTranslate(0, offset); + view.setImageMatrix(matrix); + } + + // See ImageView + private Matrix centerCropWithOffsetScalar(@NonNull View view, + @NonNull Drawable drawable, + @NonNull Matrix initial, + float offsetScalar) { + final float halfScalar = .5f; + + Matrix matrix = new Matrix(initial); + + int drawableWidth = drawable.getIntrinsicWidth(); + int drawableHeight = drawable.getIntrinsicHeight(); + + int viewWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight(); + int viewHeight = view.getHeight() - view.getPaddingTop() - view.getPaddingBottom(); + + float scale; + float dx = 0; + float dy = 0; + if (drawableWidth * viewHeight > viewWidth * drawableHeight) { + scale = (float) viewHeight / (float) drawableHeight; + dx = (viewWidth - drawableWidth * scale) * halfScalar; + } else { + scale = (float) viewWidth / (float) drawableWidth; + dy = (viewHeight - drawableHeight * scale) * halfScalar; + } + + matrix.setScale(scale, scale); + matrix.postTranslate(dx, dy); + + float y = (viewHeight - drawableHeight * scale) * (offsetScalar - halfScalar); + matrix.postTranslate(0, y); + + return matrix; + } + private Spanned pronunciationSpanned() { AudioUrlSpan span = new AudioUrlSpan(text, avPlayer, pronunciationUrl, AudioUrlSpan.ALIGN_BASELINE); diff --git a/app/src/main/res/layout/view_article_header.xml b/app/src/main/res/layout/view_article_header.xml index ea8fe21..b8cb038 100644 --- a/app/src/main/res/layout/view_article_header.xml +++ b/app/src/main/res/layout/view_article_header.xml @@ -5,8 +5,8 @@ <org.wikipedia.page.leadimages.ImageViewWithFace android:id="@+id/image" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:scaleType="centerCrop" + android:layout_height="match_parent" + android:scaleType="matrix" android:background="@color/white" android:contentDescription="@null" /> @@ -14,8 +14,8 @@ <ImageView android:id="@+id/placeholder" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:scaleType="centerCrop" + android:layout_height="match_parent" + android:scaleType="matrix" android:src="?lead_image_drawable" android:contentDescription="@null" /> @@ -32,4 +32,4 @@ android:lineSpacingMultiplier="@dimen/lead_title_leading_scalar" tools:text="Title\nSubtitle" /> -</merge> \ No newline at end of file +</merge> -- To view, visit https://gerrit.wikimedia.org/r/253065 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I17ae6c24f876a37b1c86180daf4794dfaa2696bd Gerrit-PatchSet: 1 Gerrit-Project: apps/android/wikipedia Gerrit-Branch: master Gerrit-Owner: Niedzielski <sniedziel...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits