jenkins-bot has submitted this change and it was merged.
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, 85 insertions(+), 53 deletions(-)
Approvals:
Mholloway: Looks good to me, but someone else must approve
Dbrant: Looks good to me, approved
jenkins-bot: Verified
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..7073afd 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, @Nullable 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..545ffbf 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,11 @@
private void updateParallaxScroll(int scrollY) {
int offset = Math.min(getHeight(), scrollY);
setTranslationY(-offset);
- image.setTranslationY(imageYOffset + offset / 2);
+
+ if (image.getDrawable() != null) {
+ updateImageViewParallax(image, imageYScalar, offset / 2);
+ }
+ updateImageViewParallax(placeholder, 0, offset / 2);
}
private void updateText() {
@@ -250,6 +261,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 canvasWidth = view.getWidth() - view.getPaddingLeft() -
view.getPaddingRight();
+ int canvasHeight = view.getHeight() - view.getPaddingTop() -
view.getPaddingBottom();
+
+ float scale;
+ float dx = 0;
+ float dy = 0;
+ if (drawableWidth * canvasHeight > canvasWidth * drawableHeight) {
+ scale = (float) canvasHeight / (float) drawableHeight;
+ dx = (canvasWidth - drawableWidth * scale) * halfScalar;
+ } else {
+ scale = (float) canvasWidth / (float) drawableWidth;
+ dy = (canvasHeight - drawableHeight * scale) * halfScalar;
+ }
+
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(dx, dy);
+
+ float y = (canvasHeight - 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: merged
Gerrit-Change-Id: I17ae6c24f876a37b1c86180daf4794dfaa2696bd
Gerrit-PatchSet: 4
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Niedzielski <[email protected]>
Gerrit-Reviewer: BearND <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Dbrant <[email protected]>
Gerrit-Reviewer: Mholloway <[email protected]>
Gerrit-Reviewer: Niedzielski <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits