I have an Android layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/dice_container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="top" >
<FrameLayout
android:id="#+id/die_frame_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal|top" >
<ImageView
android:id="#+id/die1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:contentDescription="#string/dice"
android:src="#drawable/d6" />
<ImageView
android:id="#+id/die_overlay_1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:alpha="0.4"
android:visibility="invisible"
android:src="#drawable/gray_shape"
android:contentDescription="#string/gray_overlay" />
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<FrameLayout
android:id="#+id/die_frame_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/die2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:contentDescription="#string/dice"
android:src="#drawable/d2" />
<ImageView
android:id="#+id/die_overlay_2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:alpha="0.4"
android:visibility="invisible"
android:src="#drawable/gray_shape"
android:contentDescription="#string/gray_overlay" />
</FrameLayout>
<FrameLayout
android:id="#+id/die_frame_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/die3"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:contentDescription="#string/dice"
android:src="#drawable/d1" />
<ImageView
android:id="#+id/die_overlay_3"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:alpha="0.4"
android:visibility="invisible"
android:src="#drawable/gray_shape"
android:contentDescription="#string/gray_overlay" />
</FrameLayout>
</LinearLayout>
</LinearLayout>
And I am trying to programmatically take care of the positions of the images. The problem is that the result is not what I want it to be, which would be three images centered in the form of a pyramid. Die number 1 is the top, and 2 and 3 bottom left and right respectively.
FrameLayout[] frames = new FrameLayout[] {(FrameLayout) findViewById(R.id.die_frame_1),
(FrameLayout) findViewById(R.id.die_frame_2),
(FrameLayout) findViewById(R.id.die_frame_3)};
Point size = getSize();
int width = size.x;
int height = size.y;
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) frames[0].getLayoutParams();
// Width will be restricted either by height or width. Dice should take max 50% of screen
int frameWidth = (int) Math.min(width * 0.44, height * 0.5 * 0.44); // Each dice 40%
params.width = frameWidth;
params.height = frameWidth; // Square, so width equals height
// Ensure that the space between each dice is equal
int frameHeightMargin = (int) (height * 0.5 - frameWidth * 2) / 3; // Divide the remainder evenly
int frameWidthMarginOuter = (int) (width - 2 * frameWidth - frameHeightMargin) / 2; // Calculate outer
int frameWidthMarginCenter = (int) frameHeightMargin / 2; // Same spacing between dice
int frameWidthMarginUpper = (int) (width - frameWidth) / 2; // Upper only
for (int i = 0; i < frames.length; i++) {
if (i == 0) {
params.setMargins(frameWidthMarginUpper, frameHeightMargin, frameWidthMarginUpper, frameWidthMarginCenter); // Gravity is centered
} else if (i == 1) { // Left dice
params.setMargins(frameWidthMarginOuter, frameWidthMarginCenter, frameWidthMarginCenter, frameHeightMargin);
} else if (i == 2) {// Right dice
params.setMargins(frameWidthMarginCenter, frameWidthMarginCenter, frameWidthMarginOuter, frameHeightMargin);
}
frames[i].setLayoutParams(params);
}
Current result:
The code is in the onCreate method by the way. I have both tried with and without requestLayout() for the FrameLayouts and the parents.
Unless you know exactly what you are doing do not use the same LayoutParams for more than one ViewGroup. The solution is very simple. Create a new LayoutParams for each die.
FrameLayout[] frames = new FrameLayout[] {(FrameLayout) findViewById(R.id.die_frame_1),
(FrameLayout) findViewById(R.id.die_frame_2),
(FrameLayout) findViewById(R.id.die_frame_3)};
Point size = getSize();
int width = size.x;
int height = size.y;
// Width will be restricted either by height or width. Dice should take max 50% of screen
int frameWidth = (int) Math.min(width * 0.44, height * 0.5 * 0.44); // Each dice 40%
// Ensure that the space between each dice is equal
int frameHeightMargin = (int) (height * 0.5 - frameWidth * 2) / 3; // Divide the remainder evenly
int frameWidthMarginOuter = (int) (width - 2 * frameWidth - frameHeightMargin) / 2; // Calculate outer
int frameWidthMarginCenter = (int) frameHeightMargin / 2; // Same spacing between dice
int frameWidthMarginUpper = (int) (width - frameWidth) / 2; // Upper only
for (int i = 0; i < frames.length; i++) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.width = frameWidth;
params.height = frameWidth; // Square, so width equals height
if (i == 0) {
params.setMargins(frameWidthMarginUpper, frameHeightMargin, frameWidthMarginUpper, frameWidthMarginCenter); // Gravity is centered
} else if (i == 1) { // Left dice
params.setMargins(frameWidthMarginOuter, frameWidthMarginCenter, frameWidthMarginCenter, frameHeightMargin);
} else if (i == 2) {// Right dice
params.setMargins(frameWidthMarginCenter, frameWidthMarginCenter, frameWidthMarginOuter, frameHeightMargin);
}
frames[i].setLayoutParams(params);
}
Related
I have a LinearLayout which is centered on the screen. It has a width less than the screen width. There are two buttons: Right-Arrow and Left-Arrow.
When the user presses the relevant button, the layout should increase its width from the relevant side. The other side should keep its position there.
Right now setting the width increases the layout from both sides equally. The layout needs to be initially centered and it has to expand from either side by user's input. (Use case is to find the width of relevant part of an image whose right and left sides have unequal borders, so the user has to mark them using my technique).
I am using following to increase width but it has the behaviour described above.
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)
llCropOverlay.getLayoutParams();
params.width = params.width + 1;
PS: This functionality was implemented in Tasker app since its early days; so it is possible.
EDIT:
Here is the layout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:gravity="top"
android:layout_gravity="top"
android:id="#+id/iv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible" />
<LinearLayout
android:id="#+id/llRightLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<Button
android:id="#+id/bLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LEFT" />
<Button
android:id="#+id/bRight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RIGHT" />
</LinearLayout>
<LinearLayout
android:layout_gravity="center_horizontal"
android:id="#+id/llCropOverlay"
android:layout_width="match_parent"
android:layout_height="70dp"
android:background="#color/colorCropOverlay"
android:orientation="vertical" />
</FrameLayout>
</LinearLayout>
The last LinearLayout (llCropOverlay) should be resized. Note that I am programatically changing the width to 300 before using resizing the buttons so I can test if the buttons are working.
I have found an almost perfect solution (there is sometimes a problem with one pixel which is annoying - any suggestions will be appreciated).
For this, we need some variables set up. Firstly, the LinearLayout called llCropOverlay must be found and identified.
Here is its xml:
<LinearLayout
android:layout_gravity="center_horizontal"
android:id="#+id/llCropOverlay"
android:layout_width="200dp"
android:layout_height="150dp"
android:background="#color/colorCropOverlay"
android:orientation="vertical" />
Now before allowing user to interact we need to find the original position of the llCropOverlay. So use this in OnCreate():
llCropOverlay.post(new Runnable() {
#Override
public void run() {
orgX = llCropOverlay.getX();
}
});
Now set up all the buttons and set a setOnTouchListener() on these buttons. Then when the listener is called, pass the touched button in the following method. Use a Handler and postDelayed() to keep calling this method till the button is pressed. Or call it once to resize by one pixel row/column.
void handleTouchOrClick(View view) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)
llCropOverlay.getLayoutParams();
switch (view.getId()) {
case R.id.bUp:
params.height = params.height - 1;
break;
case R.id.bDown:
params.height = params.height + 1;
break;
case R.id.bRight:
params.width = params.width + 1;
llCropOverlay.post(new Runnable() {
#Override
public void run() {
llCropOverlay.setX(orgX);
}
});
break;
case R.id.bRightContract:
params.width = params.width - 1;
llCropOverlay.post(new Runnable() {
#Override
public void run() {
llCropOverlay.setX(orgX);
}
});
break;
case R.id.bLeft:
params.width = params.width + 1;
orgX--;
llCropOverlay.post(new Runnable() {
#Override
public void run() {
llCropOverlay.setX(orgX);
}
});
break;
case R.id.bLeftContract:
params.width = params.width - 1;
orgX++;
llCropOverlay.post(new Runnable() {
#Override
public void run() {
llCropOverlay.setX(orgX);
}
});
break;
}
llCropOverlay.setLayoutParams(params);
}
Now here's how we actually resize the image:
For ease of users I am cropping it in two steps.
Crop from sides:
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) llCropOverlay.getLayoutParams();
float eventX = params.width;
float eventY = 0;
float[] eventXY = new float[]{eventX, eventY};
Matrix invertMatrix = new Matrix();
imageView.getImageMatrix().invert(invertMatrix);
invertMatrix.mapPoints(eventXY);
int x = Integer.valueOf((int) eventXY[0]);
int y = Integer.valueOf((int) eventXY[1]);
int height = params.height;
while (height * 3 > originalBitmap.getHeight()) {
height = height - 10;
}
croppedBitmapByWidth = Bitmap.createBitmap(originalBitmap, (int) orgX, 0,
x, height);
imageView.setImageBitmap(croppedBitmapByWidth);
crop from bottom:
float eventX2 = 0;
float eventY2 = params.height;
float[] eventXY2 = new float[]{eventX2, eventY2};
Matrix invertMatrix2 = new Matrix();
imageView.getImageMatrix().invert(invertMatrix2);
invertMatrix2.mapPoints(eventXY2);
int x2 = Integer.valueOf((int) eventXY2[0]);
int y2 = Integer.valueOf((int) eventXY2[1]);
croppedBitmapByHeight = Bitmap.createBitmap(croppedBitmapByWidth, 0, 0,
croppedBitmapByWidth.getWidth(), y2);
imageView.setImageBitmap(croppedBitmapByHeight);
I am creating an app with a recyclerview. And above the RV I have an image, which should get smaller, when i scroll. This works, but the RV scrolls also. I want that first the image gets smaller and then the recyclerview starts scrolling. But how can I do this? Here is my XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/b"
android:id="#+id/test_photo"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="test"/>
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:layout_anchor="#+id/test_photo"
android:background="#color/colorPrimary"
app:layout_anchorGravity="bottom|start">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/colorWhite"
android:textSize="30sp"
android:text="username"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/user_view_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
And this is the code to resize the image:
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
float state = 0.0f;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
Log.e("Y",Integer.toString(dy));
state+=dy;
LinearLayout img = (LinearLayout) findViewById(R.id.test_photo);
Log.e("STATE", Float.toString(state));
if(state >= 500){
img.getLayoutParams().height = minWidth;
img.getLayoutParams().width = minWidth;
img.requestLayout();
}
if(state <= 0){
img.getLayoutParams().height = imgHeight;
img.getLayoutParams().width = imgHeight;
img.requestLayout();
}
if(state > 0 && state < 500){
//up
img.getLayoutParams().height = (int)(imgHeight - ((float)(imgHeight-minWidth)/500)*state);
img.getLayoutParams().width = (int)(imgHeight - ((float)(imgHeight-minWidth)/500)*state);
img.requestLayout();
}
}
});
Thanks for the help!
EDIT:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="320dp"
android:fitsSystemWindows="true"
android:theme="#style/AppTheme.AppBarOverlay">
<com.obware.alifsto.HelpClasses.CollapsingImageLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:minHeight="108dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="#drawable/sunset" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
<ImageView
android:id="#+id/avatar"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="96dp"
android:src="#drawable/logo_blau_weiss"
android:transitionName="#string/transition_userview_image"/>
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="48dp"
android:text="Title"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold" />
<TextView
android:id="#+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="24dp"
android:text="Subtitle "
android:transitionName="#string/transition_userview_username"
android:textAppearance="?android:attr/textAppearanceMedium" />
</com.obware.alifsto.HelpClasses.CollapsingImageLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/user_interface_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView>
The way you want to do this is with CoordinatorLayout and AppBarLayout and use all that Material Design scrolling goodness.
So essentially what you do is create a specialized layout similar to CollapsingToolbarLayout. For my demo, I used code from that class as inspiration to get my collapsing image layout to work.
What makes it work is adding the layout as a direct child of AppBarLayout, then creating an AppBarLayout.OnOffsetChangeListener and registering it with the AppBarLayout. When you do this, you will get notifications when the user scrolls and the layout is scrolled up.
Another big part of this is setting a minimum height. AppBarLayout uses the minimum height to determine when to stop scrolling your layout, leaving you with a collapsed layout area.
Here's a code excerpt:
class OnOffsetChangedListener implements AppBarLayout.OnOffsetChangedListener {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
final int scrollRange = appBarLayout.getTotalScrollRange();
float offsetFactor = (float) (-verticalOffset) / (float) scrollRange;
Log.d(TAG, "onOffsetChanged(), offsetFactor = " + offsetFactor);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
if (child instanceof Toolbar) {
if (getHeight() - insetTop + verticalOffset >= child.getHeight()) {
offsetHelper.setTopAndBottomOffset(-verticalOffset); // pin
}
}
if (child.getId() == R.id.background) {
int offset = Math.round(-verticalOffset * .5F);
offsetHelper.setTopAndBottomOffset(offset); // parallax
}
if (child.getId() == R.id.avatar) {
float scaleFactor = 1F - offsetFactor * .5F ;
child.setScaleX(scaleFactor);
child.setScaleY(scaleFactor);
int topOffset = (int) ((mImageTopCollapsed - mImageTopExpanded) * offsetFactor) - verticalOffset;
int leftOffset = (int) ((mImageLeftCollapsed - mImageLeftExpanded) * offsetFactor);
child.setPivotX(0);
child.setPivotY(0);
offsetHelper.setTopAndBottomOffset(topOffset);
offsetHelper.setLeftAndRightOffset(leftOffset);
}
if (child.getId() == R.id.title) {
int topOffset = (int) ((mTitleTopCollapsed - mTitleTopExpanded) * offsetFactor) - verticalOffset;
int leftOffset = (int) ((mTitleLeftCollapsed - mTitleLeftExpanded) * offsetFactor);
offsetHelper.setTopAndBottomOffset(topOffset);
offsetHelper.setLeftAndRightOffset(leftOffset);
Log.d(TAG, "onOffsetChanged(), offsetting title top = " + topOffset + ", left = " + leftOffset);
Log.d(TAG, "onOffsetChanged(), offsetting title mTitleLeftCollapsed = " + mTitleLeftCollapsed + ", mTitleLeftExpanded = " + mTitleLeftExpanded);
}
if (child.getId() == R.id.subtitle) {
int topOffset = (int) ((mSubtitleTopCollapsed - mSubtitleTopExpanded) * offsetFactor) - verticalOffset;
int leftOffset = (int) ((mSubtitleLeftCollapsed - mSubtitleLeftExpanded) * offsetFactor);
offsetHelper.setTopAndBottomOffset(topOffset);
offsetHelper.setLeftAndRightOffset(leftOffset);
}
}
}
}
The lines child.setScaleX() and child.setScaleY() are the code that actually changes the size of the image.
Demo app is on GitHub at https://github.com/klarson2/Collapsing-Image. Enjoy.
EDIT: After adding a TabLayout I realized one mistake I made in my layout, which was to make the AppBarLayout a fixed height, then make the custom collapsing component height be match_parent. This makes it so you can't see the TabLayout that is added to the app bar. I changed the layout so that AppBarLayout height was wrap_content and the custom collapsing component had the fixed height. This makes it possible to add additional components like a TabLayout to the AppBarLayout. This has been corrected in the latest revision on GitHub.
With the following code I resize the image according to the scrolling. So that you can see it collapsed in the AppBar.
Play with the values of the duration of the animation and the value of the scaling when the AppBar is collapsed.
In my case I have the Toolbar as transparent and I manage the colors of the AppBar elements at run times.
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
/**
* Collapsed
*/
if (Math.abs(verticalOffset) == appBarLayout.getTotalScrollRange()) {
myImage.animate().scaleX((float)0.4).setDuration(3000);
myImage.animate().scaleY((float)0.4).setDuration(3000);
myImage.animate().alpha(1).setDuration(0);
/**
* Expanded
*/
} else if (verticalOffset == 0) {
myImage.animate().scaleX((float)1).setDuration(100);
myImage.animate().scaleY((float)1).setDuration(100);
myImage.animate().alpha(1).setDuration(0);
/**
* Somewhere in between
*/
} else {
final int scrollRange = appBarLayout.getTotalScrollRange();
float offsetFactor = (float) (-verticalOffset) / (float) scrollRange;
float scaleFactor = 1F - offsetFactor * .5F;
myImage.animate().scaleX(scaleFactor);
myImage.animate().scaleY(scaleFactor);
}
}
PD: This works regardless of whether the image exceeds the limits of the AppBar, as if the image were a floating button.
GL
Sources
Listener
Conditionals
Some methods
These is the result that I am after:
Basically I want to scale the 3 images so that they have the same height and all together fill the screen width. The original images will all have same height.
Can this be done using layout, without width calculations from code?
Just use Layout Weights.
In the main layout, or the layout which contains the ImageViews, put
android:weightSum="10"
and then in the individual ImageViews, put layout_weights as shown below, or upto your requirements.
This basically means the width of the images will be 25%, 55% and 20% respectively.
You can use a linear layout with weight attribute specified as shown below:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="0dp"
android:src="#drawable/bg_canvas"
android:layout_height="300dp"
android:layout_weight="0.33"/>
<ImageView
android:layout_width="0dp"
android:src="#drawable/bg_canvas"
android:layout_height="300dp"
android:layout_weight="0.33"/>
<ImageView
android:layout_width="0dp"
android:layout_height="300dp"
android:src="#drawable/bg_canvas"
android:layout_weight="0.33"/>
</LinearLayout>
Comment below if you need any further info
try this:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3"
android:orientation="horizontal">
<ImageView
android:id="#+id/imageView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
.../>
<ImageView
android:id="#+id/imageView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
.../>
<ImageView
android:id="#+id/imageView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
.../>
</LinearLayout>
the "magic" is in the weight component. you define a total weight of 3 in the layout and your image views should take a third of it, so the value is 1.
For my case the images needed to be updated at runtime, so none of the answers were exact fit.
I ended up extending LinearLayout and writing a small routine that unifies all images heights and make sure that all images together fill the LinearLayout width. In case someone is trying to achieve the same, my code looks like this:
public class MyImgLayout extends LinearLayout
{
public MyImgLayout(Context context)
{
super(context);
}
public void setup(ArrayList<String> images)
{
this.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
this.setLayoutParams(layoutParams); //set 0 height until we calculate it in onMeasure
for (String image : images) {
ImageView ivArticle = new ImageView(getContext());
setImageFromName(image, ivArticle); //this where you set the image
this.addView(ivArticle);
}
}
private void scaleImages()
{
if(getMeasuredHeight() == 0 && getMeasuredWidth() > 0) {
if (isHorizontal) {
double childRatioSum = 0;
int images = 0;
for (int i = 0; i < getChildCount(); i++) {
ImageView iv = (ImageView) getChildAt(i);
double width = iv.getDrawable().getIntrinsicWidth();
double height = iv.getDrawable().getIntrinsicHeight();
if (height > 0) {
childRatioSum += width / height;
images++;
}
}
if (childRatioSum > 0 && images == getChildCount()) {
//all images are downloaded, calculate the container height
//(add a few pixels to makes sure we fill the whole width)
double containerHeight = (int) (getMeasuredWidth() / childRatioSum) + images * 0.5;
for (int i = 0; i < getChildCount(); i++) {
ImageView iv = (ImageView) getChildAt(i);
double width = iv.getDrawable().getIntrinsicWidth();
double height = iv.getDrawable().getIntrinsicHeight();
iv.setLayoutParams(new LinearLayout.LayoutParams((int) (width * (containerHeight / height)), (int) containerHeight));
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
}
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) this.getLayoutParams();
params.width = LayoutParams.MATCH_PARENT;
params.height = (int) containerHeight;
this.setLayoutParams(params);
requestLayout();
}
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
scaleImages();
}
}
I have a RelativeLayout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:foregroundGravity="center"
android:gravity="center"
android:orientation="horizontal" >
<VideoView
android:id="#+id/videoViewPanel"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:layout_centerInParent="true"/>
</RelativeLayout>
And what I need is to show video fullscreen cropped. If I could compare to ImageView, I need to show it as crop_center.
How can I make VideoView not to auto-resize video to fit center, but crop center?
In Android's VideoView, here is a simple and easy way to achieve the same effect as ImageView.ScaleType.CENTER_CROP
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<VideoView
android:id="#+id/videoView"
android:layout_width="#dimen/dimen_0dp"
android:layout_height="#dimen/dimen_0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
In JAVA:
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
float videoRatio = mp.getVideoWidth() / (float) mp.getVideoHeight();
float screenRatio = videoView.getWidth() / (float)
videoView.getHeight();
float scaleX = videoRatio / screenRatio;
if (scaleX >= 1f) {
videoView.setScaleX(scaleX);
} else {
videoView.setScaleY(1f / scaleX);
}
}
});
In Kotlin:
videoView.setOnPreparedListener { mediaPlayer ->
val videoRatio = mediaPlayer.videoWidth / mediaPlayer.videoHeight.toFloat()
val screenRatio = videoView.width / videoView.height.toFloat()
val scaleX = videoRatio / screenRatio
if (scaleX >= 1f) {
videoView.scaleX = scaleX
} else {
videoView.scaleY = 1f / scaleX
}
}
And this worked for me. Hope this will help someone.
The solution is to use TextureView instead of VideoView(SurfaceView).
TextureView does not make any manipulations with the content to fit it ti the screen.
Here is the code sample for the solution:
//store the SurfaceTexture to set surface for MediaPlayer
mTextureView.setSurfaceTextureListener(new SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surface,
int width, int height) {
FullScreenActivity.this.mSurface = surface;
}
....
Surface s = new Surface(mSurface);
mPlayer = mp;
mp.setSurface(s);
scaleVideo(mp);//<-- this function scales video to run cropped
....
private void scaleVideo(MediaPlayer mPlayer) {
LayoutParams videoParams = (LayoutParams) mTextureView
.getLayoutParams();
DisplayMetrics dm = new DisplayMetrics();
FullScreenActivity.this.getWindowManager().getDefaultDisplay()
.getMetrics(dm);
final int height = dm.heightPixels;
final int width = dm.widthPixels;
int videoHeight = mPlayer.getVideoHeight();
int videoWidth = mPlayer.getVideoWidth();
double hRatio = 1;
hRatio = (height * 1.0 / videoHeight) / (width * 1.0 / videoWidth);
videoParams.x = (int) (hRatio <= 1 ? 0 : Math.round((-(hRatio - 1) / 2)
* width));
videoParams.y = (int) (hRatio >= 1 ? 0 : Math
.round((((-1 / hRatio) + 1) / 2) * height));
videoParams.width = width - videoParams.x - videoParams.x;
videoParams.height = height - videoParams.y - videoParams.y;
Log.e(TAG, "x:" + videoParams.x + " y:" + videoParams.y);
mTextureView.setScaleX(1.00001f);//<-- this line enables smoothing of the picture in TextureView.
mTextureView.requestLayout();
mTextureView.invalidate();
}
I just put video inside ConstraintLayout with such parameters. This helped stretch video and achieve android:scaleType="centerCrop" effect.
<VideoView
android:id="#+id/video_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_gravity="center_horizontal"
android:layout_width="0dp"
android:layout_height="0dp" />
To crop center in fullscreen you can still use a VideoView. Set the VideoView width and height to match the parent inside a RelativeLayout and adjust it to be bigger than the screen and set his position.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/rootLayout"
tools:context="com.example.Activity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_height="match_parent">
<VideoView
android:id="#+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_centerVertical="true" />
</RelativeLayout>
</RelativeLayout>
And then in onCreate:
RelativeLayout rootView=(RelativeLayout) findViewById(R.id.rootLayout);
Display display=getWindowManager().getDefaultDisplay();
Point size=new Point();
display.getSize(size);
FrameLayout.LayoutParams rootViewParams = (FrameLayout.LayoutParams) rootView.getLayoutParams();
int videoWidth=864;
int videoHeight=1280;
if ((float)videoWidth/(float)videoHeight<(float)size.x/(float)size.y) {
rootViewParams.width=size.x;
rootViewParams.height=videoHeight*size.x/videoWidth;
rootView.setX(0);
rootView.setY((rootViewParams.height-size.y)/2*-1);
} else {
rootViewParams.width=videoWidth*size.y/videoHeight;
rootViewParams.height=size.y;
rootView.setX((rootViewParams.width-size.x)/2*-1);
rootView.setY(0);
}
rootView.setLayoutParams(rootViewParams);
final VideoView mVideoView=(VideoView)findViewById(R.id.video_view);
mVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.splash));
mVideoView.requestFocus();
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
mVideoView.start();
}
});
I have found a solution: The default behavior is just like fitCenter , so I compute the video ratio(width/height) and screen ratio, and then scale the VideoView to full screen. The result is just like centerCrop .
I saw many questions on this same topic and tried by using
android:adjustViewBounds="true"
android:scaleType="fitCenter"
still my image is displaying as it is.If I change the
android:scaleType="fitCenter"
to
fitXY working fine.But as per document this one not maintains aspect ratio.So How can I change the code to work as expected?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/card"
android:layout_marginTop="25dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#drawable/card" >
<ImageView
android:id="#+id/idImage"
android:layout_width="fill_parent"
android:layout_height="110dp"
android:layout_margin="10dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="#drawable/783454" />
</LinearLayout>
</LinearLayout>
![enter image description here][1]
![enter image description here][2]
Where iam missing the concept?
Please look at this code, this is how I was scaling bitmaps to fit screen properly. Maybe it will be helpful and give you ideas regarding to your task.
private void loadImage() {
ImageView imageView = (ImageView)findViewById(R.id.imageView);
Bitmap imageBitmap = ... load original image bitmap;
Bitmap scaledBitmap = imageBitmap;
// Scaling
int imgSrcHeight = imageBitmap.getHeight();
int imgSrcWidth = imageBitmap.getWidth();
int scaledHeight = 0;
int scaledWidth = 0;
int ctnrHeight = imageView.getMeasuredHeight();
int ctnrWidth = imageView.getMeasuredWidth();
int mHeight = imgSrcHeight - ctnrHeight;
int mWidth = imgSrcWidth - ctnrWidth;
if(mHeight > 0 && mWidth > 0)
{
if(mHeight > mWidth)
{
// scale to fit height
if(mHeight > 0)
{
scaledHeight = ctnrHeight;
// if height < 0 it means it's already inside of content
int coefOverhight = (ctnrHeight * 100)/imgSrcHeight;
scaledWidth = (int)(imgSrcWidth * ((coefOverhight)/100.0));
}
}
else
{
// scale to fit width
if(mWidth > 0)
{
scaledWidth = ctnrWidth;
int coefOverwidth = (ctnrWidth * 100)/imgSrcWidth;
scaledHeight = (int)(imgSrcHeight * ((coefOverwidth)/100.0));
}
}
}
else
{
scaledHeight = imgSrcHeight;
scaledWidth = imgSrcWidth;
}
scaledBitmap = Bitmap.createScaledBitmap(imageBitmap, scaledWidth, scaledHeight, true);
imageView.setImageBitmap(scaledBitmap);
}