how can I make the drawable on a button smaller? The icon is too big, actually higher than the button. This is the code I am using:
<Button
android:background="#drawable/red_button"
android:drawableLeft="#drawable/s_vit"
android:id="#+id/ButtonTest"
android:gravity="left|center_vertical"
android:text="S-SERIES CALCULATOR"
android:textColor="#android:color/white"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp"
android:drawablePadding="10dp">
</Button>
The upper is how it should look, the lower how it looks right now.
I tried this but there is no image displayed. :-(
Resources res = getResources();
ScaleDrawable sd = new ScaleDrawable(res.getDrawable(R.drawable.s_vit), 0, 10f, 10f);
Button btn = (Button) findViewById(R.id.ButtonTest);
btn.setCompoundDrawables(sd.getDrawable(), null, null, null);
I have found a very simple and effective XML solution that doesn't require ImageButton
Make a drawable file for your image as below and use it for android:drawableLeft
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/half_overlay"
android:drawable="#drawable/myDrawable"
android:width="40dp"
android:height="40dp"
/>
</layer-list>
You can set the image size with android:width and android:height properties.
This way you could at least get the same size for different screens.
The drawback is that it is not exactly like fitXY which would scale image width to fit X and scale image height accordingly.
You should use a ImageButton and specify the image in android:src, and set android:scaletype to fitXY
Setting scaled drawable in code
Drawable drawable = getResources().getDrawable(R.drawable.s_vit);
drawable.setBounds(0, 0, (int)(drawable.getIntrinsicWidth()*0.5),
(int)(drawable.getIntrinsicHeight()*0.5));
ScaleDrawable sd = new ScaleDrawable(drawable, 0, scaleWidth, scaleHeight);
Button btn = findViewbyId(R.id.yourbtnID);
btn.setCompoundDrawables(sd.getDrawable(), null, null, null); //set drawableLeft for example
Buttons do not resize their inner images.
My solution does not require code manipulation.
It uses a layout with TextView and ImageView.
The background of the layout should have the red 3d drawable.
You may need to define the android:scaleType xml attribute.
Example:
<LinearLayout
android:id="#+id/list_item"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:padding="2dp" >
<ImageView
android:layout_width="50dp"
android:layout_height="fill_parent"
android:src="#drawable/camera" />
<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="1"
android:gravity="center_vertical"
android:text="Hello - primary" />
</LinearLayout>
BTW:
Counting on different resolution icons may result in a non predictable UI (icon too big or too small)
Text in textview (including in buttons) does not fill the component. This is an Android problem and I don't know how to solve it.
You can use it as an include.
Good luck
Use a ScaleDrawable as Abhinav suggested.
The problem is that the drawable doesn't show then - it's some sort of bug in ScaleDrawables. you'll need to change the "level" programmatically. This should work for every button:
// Fix level of existing drawables
Drawable[] drawables = myButton.getCompoundDrawables();
for (Drawable d : drawables) if (d != null && d instanceof ScaleDrawable) d.setLevel(1);
myButton.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3]);
My DiplayScaleHelper, that works perfectly:
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ScaleDrawable;
import android.widget.Button;
public class DisplayHelper {
public static void scaleButtonDrawables(Button btn, double fitFactor) {
Drawable[] drawables = btn.getCompoundDrawables();
for (int i = 0; i < drawables.length; i++) {
if (drawables[i] != null) {
if (drawables[i] instanceof ScaleDrawable) {
drawables[i].setLevel(1);
}
drawables[i].setBounds(0, 0, (int) (drawables[i].getIntrinsicWidth() * fitFactor),
(int) (drawables[i].getIntrinsicHeight() * fitFactor));
ScaleDrawable sd = new ScaleDrawable(drawables[i], 0, drawables[i].getIntrinsicWidth(), drawables[i].getIntrinsicHeight());
if(i == 0) {
btn.setCompoundDrawables(sd.getDrawable(), drawables[1], drawables[2], drawables[3]);
} else if(i == 1) {
btn.setCompoundDrawables(drawables[0], sd.getDrawable(), drawables[2], drawables[3]);
} else if(i == 2) {
btn.setCompoundDrawables(drawables[0], drawables[1], sd.getDrawable(), drawables[3]);
} else {
btn.setCompoundDrawables(drawables[0], drawables[1], drawables[2], sd.getDrawable());
}
}
}
}
}
You can call setBounds on the "compound" drawables to modify the size of the image.
Try this code to autosize the drawable of your button:
DroidUtils.scaleButtonDrawables((Button) findViewById(R.id.ButtonTest), 1.0);
defined by this function:
public final class DroidUtils {
/** scale the Drawables of a button to "fit"
* For left and right drawables: height is scaled
* eg. with fitFactor 1 the image has max. the height of the button.
* For top and bottom drawables: width is scaled:
* With fitFactor 0.9 the image has max. 90% of the width of the button
* */
public static void scaleButtonDrawables(Button btn, double fitFactor) {
Drawable[] drawables = btn.getCompoundDrawables();
for (int i = 0; i < drawables.length; i++) {
if (drawables[i] != null) {
int imgWidth = drawables[i].getIntrinsicWidth();
int imgHeight = drawables[i].getIntrinsicHeight();
if ((imgHeight > 0) && (imgWidth > 0)) { //might be -1
float scale;
if ((i == 0) || (i == 2)) { //left or right -> scale height
scale = (float) (btn.getHeight() * fitFactor) / imgHeight;
} else { //top or bottom -> scale width
scale = (float) (btn.getWidth() * fitFactor) / imgWidth;
}
if (scale < 1.0) {
Rect rect = drawables[i].getBounds();
int newWidth = (int)(imgWidth * scale);
int newHeight = (int)(imgHeight * scale);
rect.left = rect.left + (int)(0.5 * (imgWidth - newWidth));
rect.top = rect.top + (int)(0.5 * (imgHeight - newHeight));
rect.right = rect.left + newWidth;
rect.bottom = rect.top + newHeight;
drawables[i].setBounds(rect);
}
}
}
}
}
}
Be aware, that this may not be called in onCreate() of an activity, because button height and width are not (yet) available there. Call this in on onWindowFocusChanged() or use this solution to call the function.
Edited:
The first incarnation of this function did not work correctly. It used userSeven7s code to scale the image, but returning ScaleDrawable.getDrawable() does not seem to work (neither does returning ScaleDrawable) for me.
The modified code uses setBounds to provide the bounds for the image. Android fits the image into these bounds.
If you want to use 1 image and display it in different size, you can use scale drawable ( http://developer.android.com/guide/topics/resources/drawable-resource.html#Scale ).
I am doing it as below. This creates a 100x100 size image in the button independent of the input image.
drawable.bounds = Rect(0,0,100,100)
button.setCompoundDrawables(drawable, null, null, null)
Not using ScaleDrawable either. Not using button.setCompoundDrawablesRelativeWithIntrinsicBounds() solved my problem, as that seems to use intrinsic bounds (source image size) instead of the bounds you just set.
You can use different sized drawables that are used with different screen densities/sizes, etc. so that your image looks right on all devices.
See here: http://developer.android.com/guide/practices/screens_support.html#support
Did you try wrapping your image in a ScaleDrawable and then using it in your button?
Here the function which I created for scaling vector drawables. I used it for setting TextView compound drawable.
/**
* Used to load vector drawable and set it's size to intrinsic values
*
* #param context Reference to {#link Context}
* #param resId Vector image resource id
* #param tint If not 0 - colour resource to tint the drawable with.
* #param newWidth If not 0 then set the drawable's width to this value and scale
* height accordingly.
* #return On success a reference to a vector drawable
*/
#Nullable
public static Drawable getVectorDrawable(#NonNull Context context,
#DrawableRes int resId,
#ColorRes int tint,
float newWidth)
{
VectorDrawableCompat drawableCompat =
VectorDrawableCompat.create(context.getResources(), resId, context.getTheme());
if (drawableCompat != null)
{
if (tint != 0)
{
drawableCompat.setTint(ResourcesCompat.getColor(context.getResources(), tint, context.getTheme()));
}
drawableCompat.setBounds(0, 0, drawableCompat.getIntrinsicWidth(), drawableCompat.getIntrinsicHeight());
if (newWidth != 0.0)
{
float scale = newWidth / drawableCompat.getIntrinsicWidth();
float height = scale * drawableCompat.getIntrinsicHeight();
ScaleDrawable scaledDrawable = new ScaleDrawable(drawableCompat, Gravity.CENTER, 1.0f, 1.0f);
scaledDrawable.setBounds(0,0, (int) newWidth, (int) height);
scaledDrawable.setLevel(10000);
return scaledDrawable;
}
}
return drawableCompat;
}
Using "BATCH DRAWABLE IMPORT" feature you can import custom size depending upon your requirement example 20dp*20dp
Now after importing use the imported drawable_image as drawable_source for your button
It's simpler this way
It is because you did not setLevel. after you setLevel(1), it will be display as u want
I made a custom button class to achieve this.
CustomButton.java
public class CustomButton extends android.support.v7.widget.AppCompatButton {
private Drawable mDrawable;
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.CustomButton,
0, 0);
try {
float mWidth = a.getDimension(R.styleable.CustomButton_drawable_width, 0);
float mHeight = a.getDimension(R.styleable.CustomButton_drawable_width, 0);
Drawable[] drawables = this.getCompoundDrawables();
Drawable[] resizedDrawable = new Drawable[4];
for (int i = 0; i < drawables.length; i++) {
if (drawables[i] != null) {
mDrawable = drawables[i];
}
resizedDrawable[i] = getResizedDrawable(drawables[i], mWidth, mHeight);
}
this.setCompoundDrawables(resizedDrawable[0], resizedDrawable[1], resizedDrawable[2], resizedDrawable[3]);
} finally {
a.recycle();
}
}
public Drawable getmDrawable() {
return mDrawable;
}
private Drawable getResizedDrawable(Drawable drawable, float mWidth, float mHeight) {
if (drawable == null) {
return null;
}
try {
Bitmap bitmap;
bitmap = Bitmap.createBitmap((int)mWidth, (int)mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return drawable;
} catch (OutOfMemoryError e) {
// Handle the error
return null;
}
}
}
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomButton">
<attr name="drawable_width" format="dimension" />
<attr name="drawable_height" format="dimension" />
</declare-styleable>
</resources>
Usage in xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.MainActivity">
<com.example.CustomButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="#drawable/ic_hero"
android:text="Avenger"
custom:drawable_height="10dp"
custom:drawable_width="10dp" />
</RelativeLayout>
I tried the techniques of this post but didnot find any of them so attractive. My solution was to use an imageview and textview and align the imageview top and bottom to the textview. This way I got the desired result. Here's some code:
<RelativeLayout
android:id="#+id/relativeLayout1"
android:layout_width="match_parent"
android:layout_height="48dp" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignTop="#+id/textViewTitle"
android:layout_alignBottom="#+id/textViewTitle"
android:src="#drawable/ic_back" />
<TextView
android:id="#+id/textViewBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#+id/textViewTitle"
android:layout_alignBottom="#+id/textViewTitle"
android:layout_toRightOf="#+id/imageView1"
android:text="Back"
android:textColor="#color/app_red"
android:textSize="#dimen/title_size" />
</RelativeLayout>
Using Kotlin Extension
val drawable = ContextCompat.getDrawable(context, R.drawable.my_icon)
// or resources.getDrawable(R.drawable.my_icon, theme)
val sizePx = 25
drawable?.setBounds(0, 0, sizePx, sizePx)
// (left, top, right, bottom)
my_button.setCompoundDrawables(drawable, null, null, null)
I suggest creating an extension function on TextView (Button extends it) for easy reuse.
button.leftDrawable(R.drawable.my_icon, 25)
// Button extends TextView
fun TextView.leftDrawable(#DrawableRes id: Int = 0, #DimenRes sizeRes: Int) {
val drawable = ContextCompat.getDrawable(context, id)
val size = context.resources.getDimensionPixelSize(sizeRes)
drawable?.setBounds(0, 0, size, size)
this.setCompoundDrawables(drawable, null, null, null)
}
Related
I need to show some Images inside the Frame (Multiple Image Frames), I have left and top coordinates correspondence to each Image.
For example, I have Image coordinates 100 from left and 100 from top, So what i need to do is just to show the image inside the ImageView which starts from (100,100).
Can anyone tell me how could i do that without Bitmap, because i need to show the list of Frames with Multiple Images inside RecyclerView.
I have used TouchImageView.class for Zoom and Move feature for creating the Frame.
1. Below screen shot while i am selecting layout, as you can clearly seen that both the images are quite close to each other. I mean that i am selecting half portion of each image.
2. Next is the Home screen at which i need to replot that image, I am having the left and top coordinate of both the images.But How can i plot that Image inside ImageView by some custom x and y.
Code snippet for populating image inside ImageView from left and top -
img.setPadding((int) x, (int) y, 0, 0);
But still not working.
Please suggest me something, i have done google search but didn't got the appropriate solution for my requirement.
Suggestions really appreciated.
Many Thanks in advance.
Kind Regards.
Update: I have added a sample app at the end of this post to illustrate this concept.
Second update: Added some code to to sample app to accommodate xy scaling
I have changed the sample app to include code that will fit the image, whether smaller or larger than the ImageView, to the view size while respecting the aspect ratio. This may not be exactly what you are looking for, but it should get you into the ballpark. This is sample code, so there are assuredly other things to accommodate in an actual application. For instance, xScale and yScale could go negative in extreme cases. I am not sure what happens if they do, but a crash is probably the result, so a bounds check is in order.
If I understand what you are trying to do, you want a fixed size ImageView to show different parts of a
larger image by setting custom (x,y) coordinates of the image to sit at the (0,0) coordinate of the ImageView.
This is basically a cropping of the image across the top and left edges.
There are probably many different ways to do this and which one works best for you will depend on your exact needs,
but here is one way:
Use the following, or some variation, in your code:
float xOffset = -100;
float yOffset = -100;
ImageView imageView = (ImageView) findViewById(R.id.shiftedView);
Matrix matrix = new Matrix();
matrix.setTranslate(xOffset, yOffset);
imageView.setImageMatrix(matrix);
Set up your ImageView something like the following. The key here is android:scaleType="matrix"
<ImageView
android:id="#+id/shiftedView"
android:layout_width="200dp"
android:layout_height="match_parent"
android:background="#FF00CCCC"
android:scaleType="matrix"
android:src="#drawable/image" />
I hope this helps you.
Sample App
Here is a quick mock-up of this concept. This app is composed of the main activity and one layout. You will have to supply an image that is the source for the ImageView to see how this works.
The seven buttons will shift the image left, right, up and down on a click within the layout of the ImageView. The "Fit X" and "Fit Y" buttons will scale the image to the corresponding dimension available within the ImageView. The "Reset" button will set the image back to the original default setting of the ImageView.
MainActivity.java
package com.example.imageshift;
import android.graphics.Matrix;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
ImageView imageView;
float xOffset = 0f;
float yOffset = 0f;
float xScale = 1.0f;
float yScale = 1.0f;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.shiftedView);
findViewById(R.id.shiftUp).setOnClickListener(this);
findViewById(R.id.shiftDown).setOnClickListener(this);
findViewById(R.id.shiftLeft).setOnClickListener(this);
findViewById(R.id.shiftRight).setOnClickListener(this);
findViewById(R.id.shiftReset).setOnClickListener(this);
findViewById(R.id.fitX).setOnClickListener(this);
findViewById(R.id.fitY).setOnClickListener(this);
}
public void doMatrixTransformations() {
Matrix matrix = new Matrix();
matrix.setTranslate(xOffset, yOffset);
matrix.postScale(xScale, yScale);
imageView.setImageMatrix(matrix);
}
#Override
public void onClick(View view) {
final int shiftAmount = 50;
switch (view.getId()) {
case R.id.shiftUp:
yOffset -= shiftAmount;
break;
case R.id.shiftDown:
yOffset += shiftAmount;
break;
case R.id.shiftLeft:
xOffset -= shiftAmount;
break;
case R.id.shiftRight:
xOffset += shiftAmount;
break;
case R.id.fitX:
xScale = (float) imageView.getWidth() / (
(float) imageView.getDrawable().getIntrinsicWidth() + xOffset);
yScale = xScale;
break;
case R.id.fitY:
yScale = (float) imageView.getHeight() /
((float) imageView.getDrawable().getIntrinsicHeight() + yOffset);
xScale = yScale;
break;
case R.id.shiftReset:
xOffset = 0;
yOffset = 0;
xScale = 1.0f;
yScale = 1.0f;
break;
}
doMatrixTransformations();
}
}
activity_main.xml
<?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:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.example.imageshift.MainActivity">
<ImageView
android:id="#+id/shiftedView"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:padding="2dp"
android:scaleType="matrix"
android:src="#drawable/image" />
<Button
android:id="#+id/shiftUp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_toRightOf="#id/shiftedView"
android:text="Up" />
<Button
android:id="#+id/shiftDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/shiftUp"
android:layout_marginLeft="20dp"
android:layout_toRightOf="#id/shiftedView"
android:text="Down" />
<Button
android:id="#+id/shiftLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/shiftDown"
android:layout_marginLeft="20dp"
android:layout_toRightOf="#id/shiftedView"
android:text="Left" />
<Button
android:id="#+id/shiftRight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/shiftLeft"
android:layout_marginLeft="20dp"
android:layout_toRightOf="#id/shiftedView"
android:text="Right" />
<Button
android:id="#+id/fitX"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/shiftRight"
android:layout_marginLeft="20dp"
android:layout_toRightOf="#id/shiftedView"
android:text="Fit X" />
<Button
android:id="#+id/fitY"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/fitX"
android:layout_marginLeft="20dp"
android:layout_toRightOf="#id/shiftedView"
android:text="Fit Y" />
<Button
android:id="#+id/shiftReset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/fitY"
android:layout_marginLeft="20dp"
android:layout_toRightOf="#id/shiftedView"
android:text="Reset" />
</RelativeLayout>
It sounds like you want to take two images and create a new image based on some combination of the two images.
There are lots of different ways to go about this. The most straightforward ways involve using Bitmaps. There's two main approaches I can see that are straightforward to accomplish this.
Position your image views how you like within some common parent and create a bitmap of that parent to use as your resulting image.
Create a bitmap yourself by drawing the images of your imageviews based on your own calculations that should mimic the layout structure you have.
Since you already seem to be showing the two images on the screen in the way you want the resulting image to look like, the first is going to be the most straightforward and won't require you to redo the calculations that your layouts and imageviews are already performing.
What we're going to do is take advantage of the fact that every view will draw itself onto a canvas and at the end of the day is just translating itself into an image itself. To get a bitmap of your parent layout you can simply create a bitmap of the same size and then tell your parent layout to draw itself onto a canvas of the bitmap, like so:
private Bitmap getCombinedBitmap(View parentView) {
Bitmap result = Bitmap.createBitmap(parentView.getWidth(), parentView.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
parentView.draw(canvas);
return result;
}
If however you would like to not be bound by just whatever the parent layout is drawing to the screen, you can create the bitmap you want by drawing your images onto a similar canvas. Here I have implemented the first layout from your question:
private Bitmap getCombinedBitmap(Bitmap a, Bitmap b) {
int aWidth = a.getWidth() / 2;
int bWidth = b.getWidth() / 2;
int resultHeight = a.getHeight() < b.getHeight()? a.getHeight() : b.getHeight();
Bitmap result = new Bitmap(aWidth + bWidth, resultHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect aRect = new Rect(0, 0, aWidth, resultHeight);
Rect aResultRect = new Rect(0, 0, aWidth, resultHeight);
canvas.drawBitmap(a, aRect, aResultRect);
Rect bRect = new Rect(0, 0, bWidth, resultHeight);
Rect bResultRect = new Rect(bWidth, 0, bWidth, resultHeight);
canvas.drawBitmap(b, bRect, bResultRect);
return result;
}
In this example I opted for clearer code rather than more optimised code. You can optimise this by not creating as many Rects, etc and generally simplifying your calculations. But you can use this approach to recreate any of your layouts onto a single bitmap, you would just need to change the calculations.
What we are doing is first creating a bitmap that has a width of half the first image plus half the second image, and the height of the smallest of the two images. We then take a section of the first image that is the left most half width wise and as high as the result height wise and we draw that onto our result. We then do the same with the second image except this time the section is the right most half width wise.
Hope this helps.
I have to admit that I'm not 100% sure I understand what you want to do. If I understand correctly, what you need is to put an ImageView inside another element (I show an example with FrameLayout but you could use LinearLayout or RelativeLayout too). Then you set the padding on the outer element.
<!-- we put the ImageView inside a Frame so that we can change -->
<!-- the padding size to create borders -->
<FrameLayout
android:layout_width="140dp"
android:layout_height="140dp"
android:id="#+id/image_frame"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/image_view"
/>
</FrameLayout>
Then in the code: (use frame instead of img)
FrameLayout frame = (FrameLayout)itemLayout.findViewById(R.id.image_frame);
frame.setPadding((int) x, (int) y, 0, 0);
If I understand what you want to do, this will do it.
You can do it easily by merging BitMaps.
I have created a test project which loads 2 image bitmaps with Glide and then merges them in one BitMap using canvas and then set it on any ImageView.
You can also save the image in a file on SD Card to avoid heavy calculation each time. It looks like this (Gif - https://github.com/hiteshsahu/Android-Merge-Bitmaps-With-Glide/blob/master/Art/demo.gif ):-
I have used a single Image view as the background of my Frame layout and I have used seek bar to change the relative position of the movie poster Raees. You can change start X and Y coordinates of either of the bitmaps using those seekbars.
How I did it:-
This is the code snippet of how I did it. It is self-explanatory
Glide.with(context)
.load(R.drawable.raees)
.asBitmap()
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
// Add Bitmap in Array and load next bitmap
arts[0] = resource;
Glide.with(context)
.load(R.drawable.raees_edited)
.asBitmap()
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(Bitmap resource1, GlideAnimation<? super Bitmap> glideAnimation) {
// Both Bitmap loaded do the Merging
arts[1] = resource1;
//Set seekbars Max
satrtX1SeekBar.setMax(arts[0].getWidth());
satrtX2SeekBar.setMax(arts[1].getWidth());
satrtY1SeekBar.setMax(arts[0].getHeight());
satrtY2SeekBar.setMax(arts[1].getHeight());
Bitmap bmOverlay = Bitmap.createBitmap(arts[0].getWidth(), arts[0].getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmOverlay);
if (shouldDrawLeftHalf) {
// Rect(left, top, right, bottom)
canvas.drawBitmap(arts[0],
new Rect(0, 0, arts[0].getWidth() / 2, arts[0].getHeight()),
new Rect(0, 0, arts[0].getWidth() / 2, arts[0].getHeight()), null);
} else {
canvas.drawBitmap(arts[0], startX1, startY1, null);
}
if (shouldDrawRightHalf) {
// Rect(left, top, right, bottom)
canvas.drawBitmap(arts[1],
new Rect(arts[1].getWidth() / 2, 0, arts[0].getWidth(), arts[1].getHeight()),
new Rect(arts[1].getWidth() / 2, 0, arts[1].getWidth(), arts[1].getHeight()), null);
} else {
canvas.drawBitmap(arts[1], startX2, startY2, null);
}
background.setImageBitmap(bmOverlay);
// saveToDisk("Test", bmOverlay);
}
});
}
});
You can download entire project from My GitHub
https://github.com/hiteshsahu/Android-Merge-Bitmaps-With-Glide
Eventually i have found the solution of my question -
Thanks for all the help provided.
So, basically i have view height, view width, top coordinate, left coordinate, Original Image Height and Original Image Width.
I need to calculate the aspect factor regarding View and Image.
Here is the formula for that -
float newAspectFactor = 0;
if (viewHeight > viewWidth) {
//Aspect factor for Height
newAspectFactor = viewHeight / bean.getPostMedia().get(i).getImg_height();
} else {
//Aspect factor for Width
newAspectFactor = viewWidth / bean.getPostMedia().get(i).getImg_width();
}
float imgNewHeight = newAspectFactor * imageHeight;
float imgNewWidth = newAspectFactor * imageWidth;
Logger.logsError(TAG, " Image New Height : " + imgNewHeight);
Logger.logsError(TAG, " Image New Width : " + imgNewWidth);
if (imgNewHeight < viewHeight) {
newAspectFactor = viewHeight / bean.getPostMedia().get(i).getImg_height();
} else if (imgNewWidth < viewWidth) {
newAspectFactor = viewWidth / bean.getPostMedia().get(i).getImg_width();
}
Top and Left coordinates.
float x = 42.0;
float y = 0.0;
Magic starts from here.
Matrix m = img.getImageMatrix();
RectF drawableRect = new RectF(x,
y,
((x * newAspectFactor) + viewWidth) / newAspectFactor,
imageHeight);
RectF viewRect = new RectF(0,
0,
viewWidth,
viewHeight);
m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.FILL);
img.setImageMatrix(m);
img.setScaleType(ImageView.ScaleType.MATRIX);
imageLoaderNew.displayImage("IMAGE URL", img, optionsPostImg);
This is what I wish to achieve:
Clicky
The container color, the progress color, the progress background color and the rounded edge radius as well as the thickness should all be editable and modifiable.
How could this be achieved with a light weight custom UI element?
After days of research, I was able to achieve what was expected with clear crisp UI and with all the above requirements and flexibility. The exact above UI can be achieved and follow parameters can be achieved as well:
1. Progress Color
2. Progress background color
3. Container color (Color of container to be set by you, you can set color of rounded edges to match the container color)
4. Height and width of the progress bar to suit your needs.
Here's the code and steps to implement it:
I. Put this code in the attrs.xml file under the values folder
<declare-styleable name="SlantingProgressBar">
<attr name="slantingProgress" format="integer"/>
<attr name="borderRadius" format="integer"/>
<attr name="borderColor" format="integer"/>
<attr name="slantingProgressColor" format="string"/>
<attr name="progressBackgroundColor" format="string"/>
<attr name="slantingProgressFullColor" format="string"/>
</declare-styleable>
II. Create a java class like this:
public class SlantingProgressbar extends View {
private float height = 0;
private float width = 0;
private int borderRadius = 20;
private float progress = 0;
private int rawProgress = 0;
private static final String OPACITY_30_PERCENT = "#66";
private int roundedBorderColor;
private String backgroundColor = "";
private String progressColor = "";
private String progressFullColor = "#fc3d39";
public SlantingProgressbar(Context context) {
super(context);
}
public SlantingProgressbar(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SlantingProgressBar,
0, 0);
try {
setProgress(array.getInt(R.styleable.SlantingProgressBar_slantingProgress, 0));
setBackgroundColor(array.getString(R.styleable.SlantingProgressBar_progressBackgroundColor)); //Default color set in the method
setBorderRadius(array.getInt(R.styleable.SlantingProgressBar_borderRadius, 20));
setRoundedBorderColor(array.getInt(R.styleable.SlantingProgressBar_borderColor, 0));
setProgressColor(array.getString(R.styleable.SlantingProgressBar_slantingProgressColor));
} finally {
array.recycle();
}
}
public void setBorderRadius(int borderRadius) {
this.borderRadius = borderRadius;
}
public int getProgress() {
return rawProgress;
}
public void setProgress(int progress) {
if(progress >=0)
{
this.rawProgress = progress;
this.invalidate();
}
else
Log.e("ChlorophyllProgressBar", "Invalid 'progress' value detected, value should be between 0 and 100");
}
public void setRoundedBorderColor(int roundedBorderColor) {
if ( roundedBorderColor == 0) {
this.roundedBorderColor = getResources().getColor(R.color.white);
Log.e("CUSTOM_TAG", "Color set to White: " + this.roundedBorderColor);
return;
}
this.roundedBorderColor = roundedBorderColor;
Log.e("CUSTOM_TAG", "Color set to custom: " + this.roundedBorderColor);
}
private int getRoundedBorderColor()
{
return roundedBorderColor;
}
public void setSlantingProgressFullColor(String color)
{
if (TextUtils.isEmpty(progressFullColor)) {
this.progressFullColor = "#fc3d39";
return;
}
}
public void setBackgroundColor(String backgroundColor) {
if (TextUtils.isEmpty(backgroundColor)) {
this.backgroundColor = "#bfe8d4";
return;
}
this.backgroundColor = backgroundColor;
}
public void setProgressColor(String progressColor) {
if (TextUtils.isEmpty(progressColor)) {
this.progressColor = "#2bb673"; //Green
return;
}
this.progressColor = progressColor;
}
public float getViewHeight() {
return height;
}
public void setViewHeight(float height) {
this.height = height;
}
public float getViewWidth() {
return width;
}
public void setViewWidth(float width) {
this.width = width;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
height = getHeight();
width = getWidth();
progress = getProcessedProgress();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.parseColor(backgroundColor));
canvas.drawPaint(paint);
paint.setColor(getProcessedProgressColor());
paint.setAntiAlias(true);
Log.d("CUSTOM_TAG", "Height: " + height);
canvas.drawRect(0, 0, progress, height, paint);
Path triangle = new Path();
triangle.setFillType(Path.FillType.EVEN_ODD);
triangle.moveTo(progress, 0);
triangle.lineTo(progress + height, 0);
triangle.lineTo(progress, height);
triangle.close();
canvas.drawPath(triangle, paint);
drawBorders(canvas, getRoundedBorderColor());
}
private void drawBorders(Canvas canvas, int color) {
float height = getHeight();
float trueWidth = getWidth();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
//paint.setColor(getResources().getColor(R.color.white));
paint.setAntiAlias(true);
Path border = new Path();
border.moveTo(0, 0);
border.lineTo(0, height / 2);
border.quadTo(height / borderRadius, height / borderRadius, height / 2, 0);
border.lineTo(0, 0);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(0, height);
border.lineTo(height / 2, height);
border.quadTo(height / borderRadius, (height - height / borderRadius), 0, height / 2);
border.lineTo(0, height);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(trueWidth, 0);
border.lineTo(trueWidth - (height / 2), 0);
border.quadTo((trueWidth - height / borderRadius), height / borderRadius, trueWidth, height / 2);
border.lineTo(trueWidth, 0);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(trueWidth, height);
border.lineTo(trueWidth - (height / 2), height);
border.quadTo((trueWidth - height / borderRadius), (height - height / borderRadius), trueWidth, height / 2);
border.lineTo(trueWidth, height);
canvas.drawPath(border, paint);
//Adding 1 pixel color
Paint paint1 = new Paint();
paint1.setStyle(Paint.Style.FILL);
int fadedColor = (color & 0x00FFFFFF) | 0x66000000;
Log.d("CUSTOM_TAG", "Faded Color Code: " + fadedColor);
paint1.setColor(fadedColor);
canvas.drawRect(0, 0, 1, height, paint1);
canvas.drawRect(trueWidth-1, 0, trueWidth, height, paint1);
}
private float getProcessedProgress()
{
return (rawProgress == 99) ? ((getWidth() * 98) / 100) : ((getWidth() * rawProgress) / 100);
}
private int getProcessedProgressColor()
{
if(rawProgress > 100)
{
return Color.parseColor(progressFullColor);
}
else
{
return Color.parseColor(progressColor);
}
}
}
III. To use the layout in your xml file:
<com.whatever.package.SlantingProgressbar
android:id="#+id/progressbar_detail"
android:layout_width="match_parent"
android:layout_height="#dimen/dimension1"
android:layout_alignParentLeft="true"
slanting_progress:borderColor="#color/darkgray"
android:layout_below="#id/alphacon_detail"
android:layout_marginBottom="#dimen/budget_list_item_paddingBottom"
android:progress="50" />
I'm sharing this code after a little while, so I might have missed out a thing or two, I'm pretty sure you can get that worked out, please feel free to correct me.
Explanation:
We're using the 'draw' methods in java to implement this feature. The advantage is that, drawing a UI element gives us a sharp and clear UI no matter how big or small you make it.
There might be some hardcoded values, so be sure to edit those before implementing.
Good luck and don't forget to up-vote if this post helps you. Thanks! :)
I'll post an answer here to show some improvements on your code.
You should avoid creating new objects during draw.
draw is called several times again and again to redraw your custom element and all those calls to new Paint() are creating new objects, that needs new memory allocation, and it drives the garbage collector crazy and makes your View much more resource intensive and probably will cause lag on scrolling elements such as RecyclerView.
Alternatively you should have them declared as private Paint border and then private Paint triangle, etc, etc. And then you should initialise the values of All the paints in a separate method and only if the parameters changed. An example code:
private boolean initPaint = false;
private void initPaintsIfNecessary(){
if(!initPaint) return;
initPaint = false;
triangle = new Paint();
triangle.set.... etc
border = new Paint();
border.set.... etc
}
then on all the methods setRoundedBorderColor, setProgressColor, etc. You call initPaint = true; and on the beginning of draw you call initPaintsIfNecessary();. This will avoid all the extra garbage collector work and will allow the UI of your app to run much smoother.
That also includes all the Paint inside drawBorders method.
use format="color" instead ofstring`.
Calling Color.parse(String) is a very slow call and it is very error prone. Alternatively you should the correct color element, like following:
<attr name="slantingProgressColor" format="color"/>
that not just is the correct way, but gives you a color preview on the editor, can be indexed on app style parameters and avoid this inefficient call to parse
then of course you should adjust or method appriately. For example:
setProgressColor(array.getColor(R.styleable.SlantingProgressBar_slantingProgressColor));
getColor will return an integer that can be directly used in paint.setColor(int);
I hope those tips can help you (and others in the community) to create better more efficient View elements. Happy coding!
I know this this old question to answer but this answer may helpful..
You can use drawArc method to achieve this..
RectF oval = new RectF();
oval.set(left, top ,right, bottom);
canvas.drawArc(oval, 270, 360, false, paint);
Google has shown some nice ways that elevation effect are shown on Lollipop here.
android:elevation="2dp"
for buttons,
android:stateListAnimator="#anim/button_state_list_animator"
How can I mimic the elevation effect on pre-Lollipop versions without 3rd party library?
You can mimic the elevation on pre-Lollipop with a official method.
I achieve same effect using,
android:background="#android:drawable/dialog_holo_light_frame"
My tested output:
reference - https://stackoverflow.com/a/25683148/3879847
Thanks to user #Repo..
Update : If you want change color of this drawable try #Irfan answer below ↓
https://stackoverflow.com/a/40815944/3879847
You can't mimic the elevation on pre-Lollipop with a official method.
You can use some drawables to make the shadow in your component. Google uses this way in CardView for example.
The ViewCompat.setElevation(View, int) currently creates the shadow only on API21+. If you check the code behind, this method calls:
API 21+:
#Override
public void setElevation(View view, float elevation) {
ViewCompatLollipop.setElevation(view, elevation);
}
API < 21
#Override
public void setElevation(View view, float elevation) {
}
You can either hack it using a card-view:
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/btnGetStuff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:cardBackgroundColor="#color/accent"
>
<!-- you could also add image view here for icon etc. -->
<TextView
android:id="#+id/txtGetStuff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/textSize_small"
android:textColor="#color/primary_light"
android:freezesText="true"
android:text="Get Stuff"
android:maxWidth="120dp"
android:singleLine="true"
android:ellipsize="end"
android:maxLines="1"
/></android.support.v7.widget.CardView>
Or look at using this third party library: https://github.com/rey5137/Material (see wiki article on button https://github.com/rey5137/Material/wiki/Button)
To bring dynamic, animated shadows to pre-Lollipop devices you have to:
Draw a black shape of a view to a bitmap
Blur that shape using elevation as a radius. You can do that using RenderScript. It's not exactly the method Lollipop's using, but gives good results and is easy to add to existing views.
Draw that blurred shape beneath the view. Probably the best place is the drawChild method.
You also have to override setElevation and setTranslationZ, override child view drawing in layouts, turn off clip-to-padding and implement state animators.
That's a lot of work, but it gives the best looking, dynamic shadows with response animations. I'm not sure why you'd like to achieve that without third party libraries. If you wish, you can analyze sources of Carbon and port the parts you'd like to have in your app:
Shadow generation
private static void blurRenderScript(Bitmap bitmap, float radius) {
Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
Allocation outAllocation = Allocation.createTyped(renderScript, inAllocation.getType());
blurShader.setRadius(radius);
blurShader.setInput(inAllocation);
blurShader.forEach(outAllocation);
outAllocation.copyTo(bitmap);
}
public static Shadow generateShadow(View view, float elevation) {
if (!software && renderScript == null) {
try {
renderScript = RenderScript.create(view.getContext());
blurShader = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
} catch (RSRuntimeException ignore) {
software = true;
}
}
ShadowView shadowView = (ShadowView) view;
CornerView cornerView = (CornerView) view;
boolean isRect = shadowView.getShadowShape() == ShadowShape.RECT ||
shadowView.getShadowShape() == ShadowShape.ROUND_RECT && cornerView.getCornerRadius() < view.getContext().getResources().getDimension(R.dimen.carbon_1dip) * 2.5;
int e = (int) Math.ceil(elevation);
Bitmap bitmap;
if (isRect) {
bitmap = Bitmap.createBitmap(e * 4 + 1, e * 4 + 1, Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
shadowCanvas.drawRect(e, e, e * 3 + 1, e * 3 + 1, paint);
blur(bitmap, elevation);
return new NinePatchShadow(bitmap, elevation);
} else {
bitmap = Bitmap.createBitmap((int) (view.getWidth() / SHADOW_SCALE + e * 2), (int) (view.getHeight() / SHADOW_SCALE + e * 2), Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
if (shadowView.getShadowShape() == ShadowShape.ROUND_RECT) {
roundRect.set(e, e, (int) (view.getWidth() / SHADOW_SCALE - e), (int) (view.getHeight() / SHADOW_SCALE - e));
shadowCanvas.drawRoundRect(roundRect, e, e, paint);
} else {
int r = (int) (view.getWidth() / 2 / SHADOW_SCALE);
shadowCanvas.drawCircle(r + e, r + e, r, paint);
}
blur(bitmap, elevation);
return new Shadow(bitmap, elevation);
}
}
Drawing a view with a shadow
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (!child.isShown())
return super.drawChild(canvas, child, drawingTime);
if (!isInEditMode() && child instanceof ShadowView && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH) {
ShadowView shadowView = (ShadowView) child;
Shadow shadow = shadowView.getShadow();
if (shadow != null) {
paint.setAlpha((int) (ShadowGenerator.ALPHA * ViewHelper.getAlpha(child)));
float childElevation = shadowView.getElevation() + shadowView.getTranslationZ();
float[] childLocation = new float[]{(child.getLeft() + child.getRight()) / 2, (child.getTop() + child.getBottom()) / 2};
Matrix matrix = carbon.internal.ViewHelper.getMatrix(child);
matrix.mapPoints(childLocation);
int[] location = new int[2];
getLocationOnScreen(location);
float x = childLocation[0] + location[0];
float y = childLocation[1] + location[1];
x -= getRootView().getWidth() / 2;
y += getRootView().getHeight() / 2; // looks nice
float length = (float) Math.sqrt(x * x + y * y);
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
x / length * childElevation / 2,
y / length * childElevation / 2);
canvas.translate(
child.getLeft(),
child.getTop());
canvas.concat(matrix);
canvas.scale(ShadowGenerator.SHADOW_SCALE, ShadowGenerator.SHADOW_SCALE);
shadow.draw(canvas, child, paint);
canvas.restoreToCount(saveCount);
}
}
if (child instanceof RippleView) {
RippleView rippleView = (RippleView) child;
RippleDrawable rippleDrawable = rippleView.getRippleDrawable();
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless) {
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
child.getLeft(),
child.getTop());
rippleDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
return super.drawChild(canvas, child, drawingTime);
}
Elevation API backported to pre-Lollipop
private float elevation = 0;
private float translationZ = 0;
private Shadow shadow;
#Override
public float getElevation() {
return elevation;
}
public synchronized void setElevation(float elevation) {
if (elevation == this.elevation)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setElevation(elevation);
this.elevation = elevation;
if (getParent() != null)
((View) getParent()).postInvalidate();
}
#Override
public float getTranslationZ() {
return translationZ;
}
public synchronized void setTranslationZ(float translationZ) {
if (translationZ == this.translationZ)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setTranslationZ(translationZ);
this.translationZ = translationZ;
if (getParent() != null)
((View) getParent()).postInvalidate();
}
#Override
public ShadowShape getShadowShape() {
if (cornerRadius == getWidth() / 2 && getWidth() == getHeight())
return ShadowShape.CIRCLE;
if (cornerRadius > 0)
return ShadowShape.ROUND_RECT;
return ShadowShape.RECT;
}
#Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setTranslationZ(enabled ? 0 : -elevation);
}
#Override
public Shadow getShadow() {
float elevation = getElevation() + getTranslationZ();
if (elevation >= 0.01f && getWidth() > 0 && getHeight() > 0) {
if (shadow == null || shadow.elevation != elevation)
shadow = ShadowGenerator.generateShadow(this, elevation);
return shadow;
}
return null;
}
#Override
public void invalidateShadow() {
shadow = null;
if (getParent() != null && getParent() instanceof View)
((View) getParent()).postInvalidate();
}
Create a 9-patch image with stretchable patches defined on an image with shadow around it.
Add this 9-patch image as a background of your button with a padding so that the shadow is visible.
You can find some pre-defined 9-patch (.9.png) images here or here from where you can select, customize and copy to your project's drawable.
to add #Ranjith Kumar answer
To add background color to the drawable (example button background color), we need to get drawable programatically.
first get the drawable
Drawable drawable = getResources().getDrawable(android.R.drawable.dialog_holo_light_frame);
set the color
drawable.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.color_primary), PorterDuff.Mode.MULTIPLY));
then set it to the view.
view.setBackgroundDrawable(drawable);
in case anyone searching.
u can easily simulate it by declaring a drawable like this -
shadow.xml
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
>
<gradient android:type="linear" android:angle="270" android:startColor="#b6b6b6" android:endColor="#ffffff"/>
</shape>
and use it int ur main xml like -
android:background="#drawable/shadow"
I'm trying to create a method for resizing multi-line text in a TextView such that it fits within the bounds (both the X and Y dimensions) of the TextView.
At present, I have something, but all it does is resize the text such that just the first letter/character of the text fills the dimensions of the TextView (i.e. only the first letter is viewable, and it's huge). I need it to fit all the lines of the text within the bounds of the TextView.
Here is what I have so far:
public static void autoScaleTextViewTextToHeight(TextView tv)
{
final float initSize = tv.getTextSize();
//get the width of the view's back image (unscaled)....
float minViewHeight;
if(tv.getBackground()!=null)
{
minViewHeight = tv.getBackground().getIntrinsicHeight();
}
else
{
minViewHeight = 10f;//some min.
}
final float maxViewHeight = tv.getHeight() - (tv.getPaddingBottom()+tv.getPaddingTop())-12;// -12 just to be sure
final String s = tv.getText().toString();
//System.out.println(""+tv.getPaddingTop()+"/"+tv.getPaddingBottom());
if(minViewHeight >0 && maxViewHeight >2)
{
Rect currentBounds = new Rect();
tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
//System.out.println(""+initSize);
//System.out.println(""+maxViewHeight);
//System.out.println(""+(currentBounds.height()));
float resultingSize = 1;
while(currentBounds.height() < maxViewHeight)
{
resultingSize ++;
tv.setTextSize(resultingSize);
tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
//System.out.println(""+(currentBounds.height()+tv.getPaddingBottom()+tv.getPaddingTop()));
//System.out.println("Resulting: "+resultingSize);
}
if(currentBounds.height()>=maxViewHeight)
{
//just to be sure, reduce the value
tv.setTextSize(resultingSize-1);
}
}
}
I think the problem is in the use of tv.getPaint().getTextBounds(...). It always returns small numbers for the text bounds... small relative to the tv.getWidth() and tv.getHeight() values... even if the text size is far larger than the width or height of the TextView.
The AutofitTextView library from MavenCentral handles this nicely. The source hosted on Github(1k+ stars) at https://github.com/grantland/android-autofittextview
Add the following to your app/build.gradle
repositories {
mavenCentral()
}
dependencies {
implementation 'me.grantland:autofittextview:0.2.+'
}
Enable any View extending TextView in code:
AutofitHelper.create(textView);
Enable any View extending TextView in XML:
<me.grantland.widget.AutofitLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
/>
</me.grantland.widget.AutofitLayout>
Use the built in Widget in code or XML:
<me.grantland.widget.AutofitTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
/>
New since Android O:
https://developer.android.com/preview/features/autosizing-textview.html
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="100sp"
android:autoSizeStepGranularity="2sp"
/>
I have played with this for quite some time, trying to get my font sizes correct on a wide variety of 7" tablets (kindle fire, Nexus7, and some inexpensive ones in China with low-res screens) and devices.
The approach that finally worked for me is as follows. The "32" is an arbitrary factor that basically gives about 70+ characters across a 7" tablet horizontal line, which is a font size I was looking for. Adjust accordingly.
textView.setTextSize(getFontSize(activity));
public static int getFontSize (Activity activity) {
DisplayMetrics dMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dMetrics);
// lets try to get them back a font size realtive to the pixel width of the screen
final float WIDE = activity.getResources().getDisplayMetrics().widthPixels;
int valueWide = (int)(WIDE / 32.0f / (dMetrics.scaledDensity));
return valueWide;
}
I was able to answer my own question using the following code (see below), but my solution was very specific to the application. For instance, this will probably only look good and/or work for a TextView sized to approx. 1/2 the screen (with also a 40px top margin and 20px side margins... no bottom margin).
The using this approach though, you can create your own similar implementation. The static method basically just looks at the number of characters and determines a scaling factor to apply to the TextView's text size, and then incrementally increases the text size until the overall height (an estimated height -- using the width of the text, the text height, and the width of the TextView) is just below that of the TextView. The parameters necessary to determine the scaling factor (i.e. the if/else if statements) were set by guess-and-check. You'll likely have to play around with the numbers to make it work for your particular application.
This isn't the most elegant solution, though it was easy to code and it works for me. Does anyone have a better approach?
public static void autoScaleTextViewTextToHeight(final TextView tv, String s)
{
float currentWidth=tv.getPaint().measureText(s);
int scalingFactor = 0;
final int characters = s.length();
//scale based on # of characters in the string
if(characters<5)
{
scalingFactor = 1;
}
else if(characters>=5 && characters<10)
{
scalingFactor = 2;
}
else if(characters>=10 && characters<15)
{
scalingFactor = 3;
}
else if(characters>=15 && characters<20)
{
scalingFactor = 3;
}
else if(characters>=20 && characters<25)
{
scalingFactor = 3;
}
else if(characters>=25 && characters<30)
{
scalingFactor = 3;
}
else if(characters>=30 && characters<35)
{
scalingFactor = 3;
}
else if(characters>=35 && characters<40)
{
scalingFactor = 3;
}
else if(characters>=40 && characters<45)
{
scalingFactor = 3;
}
else if(characters>=45 && characters<50)
{
scalingFactor = 3;
}
else if(characters>=50 && characters<55)
{
scalingFactor = 3;
}
else if(characters>=55 && characters<60)
{
scalingFactor = 3;
}
else if(characters>=60 && characters<65)
{
scalingFactor = 3;
}
else if(characters>=65 && characters<70)
{
scalingFactor = 3;
}
else if(characters>=70 && characters<75)
{
scalingFactor = 3;
}
else if(characters>=75)
{
scalingFactor = 5;
}
//System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
//the +scalingFactor is important... increase this if nec. later
while((((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor)*tv.getTextSize())<tv.getHeight())
{
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, tv.getTextSize()+0.25f);
currentWidth=tv.getPaint().measureText(s);
//System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
}
tv.setText(s);
}
Thanks.
I had the same problem and wrote a class that seems to work for me. Basically, I used a static layout to draw the text in a separate canvas and remeasure until I find a font size that fits. You can see the class posted in the topic below. I hope it helps.
Auto Scale TextView Text to Fit within Bounds
Stumbled upon this whilst looking for a solution myself... I'd tried all the other solutions out there that I could see on stack overflow etc but none really worked so I wrote my own.
Basically by wrapping the text view in a custom linear layout I've been able to successfully measure the text properly by ensuring it is measured with a fixed width.
<!-- TextView wrapped in the custom LinearLayout that expects one child TextView -->
<!-- This view should specify the size you would want the text view to be displayed at -->
<com.custom.ResizeView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_margin="10dp"
android:layout_weight="1"
android:orientation="horizontal" >
<TextView
android:id="#+id/CustomTextView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
</com.custom.ResizeView>
Then the linear layout code
public class ResizeView extends LinearLayout {
public ResizeView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ResizeView(Context context) {
super(context);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// oldWidth used as a fixed width when measuring the size of the text
// view at different font sizes
final int oldWidth = getMeasuredWidth() - getPaddingBottom() - getPaddingTop();
final int oldHeight = getMeasuredHeight() - getPaddingLeft() - getPaddingRight();
// Assume we only have one child and it is the text view to scale
TextView textView = (TextView) getChildAt(0);
// This is the maximum font size... we iterate down from this
// I've specified the sizes in pixels, but sp can be used, just modify
// the call to setTextSize
float size = getResources().getDimensionPixelSize(R.dimen.solutions_view_max_font_size);
for (int textViewHeight = Integer.MAX_VALUE; textViewHeight > oldHeight; size -= 0.1f) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
// measure the text views size using a fixed width and an
// unspecified height - the unspecified height means measure
// returns the textviews ideal height
textView.measure(MeasureSpec.makeMeasureSpec(oldWidth, MeasureSpec.EXACTLY), MeasureSpec.UNSPECIFIED);
textViewHeight = textView.getMeasuredHeight();
}
}
}
Hope this helps someone.
maybe try setting setHoriztonallyScrolling() to true before taking text measurements so that the textView doesn't try to layout your text on multiple lines
One way would be to specify different sp dimensions for each of the generalized screen sizes. For instance, provide 8sp for small screens, 12sp for normal screens, 16 sp for large and 20 sp for xlarge. Then just have your layouts refer to #dimen text_size or whatever and you can rest assured, as density is taken care of via the sp unit. See the following link for more info on this approach.
http://www.developer.android.com/guide/topics/resources/more-resources.html#Dimension
I must note, however, that supporting more languages means more work during the testing phase, especially if you're interested in keeping text on one line, as some languages have much longer words. In that case, make a dimens.xml file in the values-de-large folder, for example, and tweak the value manually. Hope this helps.
Here is a solution that I created based on some other feedback. This solution allows you to set the size of the text in XML which will be the max size and it will adjust itself to fit the view height.
Size Adjusting TextView
private float findNewTextSize(int width, int height, CharSequence text) {
TextPaint textPaint = new TextPaint(getPaint());
float targetTextSize = textPaint.getTextSize();
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
while(textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 1, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
return targetTextSize;
}
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
paint.setTextSize(textSize);
StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
If your only requirement is to have the text automatically split and continue in the next line and the height is not important then just have it like this.
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:maxEms="integer"
android:width="integer"/>
This will have your TextView wrap to it's content vertically depending on your maxEms value.
Check if my solution helps you:
Auto Scale TextView Text to Fit within Bounds
I found that this worked well for me. see: https://play.google.com/store/apps/details?id=au.id.rupert.chauffeurs_name_board&hl=en
Source Code at http://www.rupert.id.au/chauffeurs_name_board/verson2.php
http://catchthecows.com/?p=72 and https://github.com/catchthecows/BigTextButton
This is based on mattmook's answer. It worked well on some devices, but not on all. I moved the resizing to the measuring step, made the maximum font size a custom attribute, took margins into account, and extended FrameLayout instead of LineairLayout.
public class ResizeView extends FrameLayout {
protected float max_font_size;
public ResizeView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.ResizeView,
0, 0);
max_font_size = a.getDimension(R.styleable.ResizeView_maxFontSize, 30.0f);
}
public ResizeView(Context context) {
super(context);
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
// Use the parent's code for the first measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Assume we only have one child and it is the text view to scale
final TextView textView = (TextView) getChildAt(0);
// Check if the default measure resulted in a fitting textView
LayoutParams childLayout = (LayoutParams) textView.getLayoutParams();
final int textHeightAvailable = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - childLayout.topMargin - childLayout.bottomMargin;
int textViewHeight = textView.getMeasuredHeight();
if (textViewHeight < textHeightAvailable) {
return;
}
final int textWidthSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight() - childLayout.leftMargin - childLayout.rightMargin,
MeasureSpec.EXACTLY);
final int textHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
for (float size = max_font_size; size >= 1.05f; size-=0.1f) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
textView.measure(textWidthSpec, textHeightSpec);
textViewHeight = textView.getMeasuredHeight();
if (textViewHeight <= textHeightAvailable) {
break;
}
}
}
}
And this in attrs.xml:
<declare-styleable name="ResizeView">
<attr name="maxFontSize" format="reference|dimension"/>
</declare-styleable>
And finally used like this:
<PACKAGE_NAME.ui.ResizeView xmlns:custom="http://schemas.android.com/apk/res/PACKAGE_NAME"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:padding="5dp"
custom:maxFontSize="#dimen/normal_text">
<TextView android:id="#+id/tabTitle2"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</PACKAGE_NAME.ui.ResizeView>
Try this...
tv.setText("Give a very large text anc check , this xample is very usefull");
countLine=tv.getLineHeight();
System.out.println("LineCount " + countLine);
if (countLine>=40){
tv.setTextSize(15);
}
On a layout I want to scale the background image (keeping its aspect ratio) to the space allocated when the page gets created. Does anyone have an idea how to do this?
I am using layout.setBackgroundDrawable() and a BitmapDrawable() to set gravity for clipping and filling, but don't see any option for scaling.
To customize background image scaling create a resource like this:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center"
android:src="#drawable/list_bkgnd" />
Then it will be centered in the view if used as background. There are also other flags: http://developer.android.com/guide/topics/resources/drawable-resource.html
Haven't tried to do exactly what you want, but you can scale an ImageView using android:scaleType="fitXY"
and it will be sized to fit into whatever size you give the ImageView.
So you could create a FrameLayout for your layout, put the ImageView inside it, and then whatever other content you need in the FrameLayout as well w/ a transparent background.
<FrameLayout
android:layout_width="fill_parent" android:layout_height="fill_parent">
<ImageView
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:src="#drawable/back" android:scaleType="fitXY" />
<LinearLayout>your views</LinearLayout>
</FrameLayout>
There is an easy way to do this from the drawable:
your_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="#color/bg_color"/>
<item>
<bitmap
android:gravity="center|bottom|clip_vertical"
android:src="#drawable/your_image" />
</item>
</layer-list>
The only downside is that if there is not enough space, your image won't be fully shown, but it will be clipped, I couldn't find an way to do this directly from a drawable. But from the tests I did it works pretty well, and it doesn't clip that much of the image. You could play more with the gravity options.
Another way will be to just create an layout, where you will use an ImageView and set the scaleType to fitCenter.
Use image as background sized to layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/imgPlaylistItemBg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:maxHeight="0dp"
android:scaleType="fitXY"
android:src="#drawable/img_dsh" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</FrameLayout>
To keep the aspect ratio you have to use android:scaleType=fitCenter or fitStart etc. Using fitXY will not keep the original aspect ratio of the image!
Note this works only for images with a src attribute, not for the background image.
When you set the Drawable of an ImageView by using the setBackgroundDrawable method, the image will always be scaled. Parameters as adjustViewBounds or different ScaleTypes will just be ignored. The only solution to keep the aspect ratio I found, is to resize the ImageView after loading your drawable. Here is the code snippet I used:
// bmp is your Bitmap object
int imgHeight = bmp.getHeight();
int imgWidth = bmp.getWidth();
int containerHeight = imageView.getHeight();
int containerWidth = imageView.getWidth();
boolean ch2cw = containerHeight > containerWidth;
float h2w = (float) imgHeight / (float) imgWidth;
float newContainerHeight, newContainerWidth;
if (h2w > 1) {
// height is greater than width
if (ch2cw) {
newContainerWidth = (float) containerWidth;
newContainerHeight = newContainerWidth * h2w;
} else {
newContainerHeight = (float) containerHeight;
newContainerWidth = newContainerHeight / h2w;
}
} else {
// width is greater than height
if (ch2cw) {
newContainerWidth = (float) containerWidth;
newContainerHeight = newContainerWidth / h2w;
} else {
newContainerWidth = (float) containerHeight;
newContainerHeight = newContainerWidth * h2w;
}
}
Bitmap copy = Bitmap.createScaledBitmap(bmp, (int) newContainerWidth, (int) newContainerHeight, false);
imageView.setBackgroundDrawable(new BitmapDrawable(copy));
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
imageView.setLayoutParams(params);
imageView.setMaxHeight((int) newContainerHeight);
imageView.setMaxWidth((int) newContainerWidth);
In the code snippet above is bmp the Bitmap object that is to be shown and imageView is the ImageView object
An important thing to note is the change of the layout parameters. This is necessary because setMaxHeight and setMaxWidth will only make a difference if the width and height are defined to wrap the content, not to fill the parent. Fill parent on the other hand is the desired setting at the beginning, because otherwise containerWidth and containerHeight will both have values equal to 0.
So, in your layout file you will have something like this for your ImageView:
...
<ImageView android:id="#+id/my_image_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
...
This is not the most performant solution, but as somebody suggested instead of background you can create FrameLayout or RelativeLayout and use ImageView as pseudo background - other elements will be position simply above it:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<ImageView
android:id="#+id/ivBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:scaleType="fitStart"
android:src="#drawable/menu_icon_exit" />
<Button
android:id="#+id/bSomeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="61dp"
android:layout_marginTop="122dp"
android:text="Button" />
</RelativeLayout>
The problem with ImageView is that only scaleTypes available are:
CENTER, CENTER_CROP, CENTER_INSIDE, FIT_CENTER,FIT_END, FIT_START, FIT_XY, MATRIX
(http://etcodehome.blogspot.de/2011/05/android-imageview-scaletype-samples.html)
and to "scale the background image (keeping its aspect ratio)" in some cases, when you want an image to fill the whole screen (for example background image) and aspect ratio of the screen is different than image's, the necessary scaleType is kind of TOP_CROP, because:
CENTER_CROP centers the scaled image instead of aligning the top edge to the top edge of the image view and FIT_START fits the screen height and not fill the width. And as user Anke noticed FIT_XY doesn't keep aspect ratio.
Gladly somebody has extended ImageView to support TOP_CROP
public class ImageViewScaleTypeTopCrop extends ImageView {
public ImageViewScaleTypeTopCrop(Context context) {
super(context);
setup();
}
public ImageViewScaleTypeTopCrop(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
public ImageViewScaleTypeTopCrop(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
private void setup() {
setScaleType(ScaleType.MATRIX);
}
#Override
protected boolean setFrame(int frameLeft, int frameTop, int frameRight, int frameBottom) {
float frameWidth = frameRight - frameLeft;
float frameHeight = frameBottom - frameTop;
if (getDrawable() != null) {
Matrix matrix = getImageMatrix();
float scaleFactor, scaleFactorWidth, scaleFactorHeight;
scaleFactorWidth = (float) frameWidth / (float) getDrawable().getIntrinsicWidth();
scaleFactorHeight = (float) frameHeight / (float) getDrawable().getIntrinsicHeight();
if (scaleFactorHeight > scaleFactorWidth) {
scaleFactor = scaleFactorHeight;
} else {
scaleFactor = scaleFactorWidth;
}
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
}
return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
}
}
https://stackoverflow.com/a/14815588/2075875
Now IMHO would be perfect if somebody wrote custom Drawable which scales image like that. Then it could be used as background parameter.
Reflog suggests to prescale drawable before using it. Here is instruction how to do it:
Java (Android): How to scale a drawable without Bitmap?
Although it has disadvantage, that upscaled drawable/bitmap will use more RAM, while scaling on the fly used by ImageView doesn't require more memory. Advantage could be less processor load.
The Below code make the bitmap perfectly with same size of the imageview. Get the bitmap image height and width and then calculate the new height and width with the help of imageview's parameters. That give you required image with best aspect ratio.
int bwidth=bitMap1.getWidth();
int bheight=bitMap1.getHeight();
int swidth=imageView_location.getWidth();
int sheight=imageView_location.getHeight();
new_width=swidth;
new_height = (int) Math.floor((double) bheight *( (double) new_width / (double) bwidth));
Bitmap newbitMap = Bitmap.createScaledBitmap(bitMap1,new_width,new_height, true);
imageView_location.setImageBitmap(newbitMap)
What Dweebo proposed works. But in my humble opinion it is unnecessary.
A background drawable scales well by itself. The view should have fixed width and height, like in the following example:
< RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/black">
<LinearLayout
android:layout_width="500dip"
android:layout_height="450dip"
android:layout_centerInParent="true"
android:background="#drawable/my_drawable"
android:orientation="vertical"
android:padding="30dip"
>
...
</LinearLayout>
< / RelativeLayout>
One option to try is to put the image in the drawable-nodpi folder and set background of a layout to the drawable resource id.
This definitely works with scaling down, I haven't tested with scaling up though.
Kotlin:
If you needed to draw a bitmap in a View, scaled to FIT.
You can do the proper calculations to set bm the height equal to the container and adjust width, in the case bm width to height ratio is less than container width to height ratio, or the inverse in the opposite scenario.
Images:
// binding.fragPhotoEditDrawCont is the RelativeLayout where is your view
// bm is the Bitmap
val ch = binding.fragPhotoEditDrawCont.height
val cw = binding.fragPhotoEditDrawCont.width
val bh = bm.height
val bw = bm.width
val rc = cw.toFloat() / ch.toFloat()
val rb = bw.toFloat() / bh.toFloat()
if (rb < rc) {
// Bitmap Width to Height ratio is less than Container ratio
// Means, bitmap should pin top and bottom, and have some space on sides.
// _____ ___
// container = |_____| bm = |___|
val bmHeight = ch - 4 //4 for container border
val bmWidth = rb * bmHeight //new width is bm_ratio * bm_height
binding.fragPhotoEditDraw.layoutParams = RelativeLayout.LayoutParams(bmWidth.toInt(), bmHeight)
}
else {
val bmWidth = cw - 4 //4 for container border
val bmHeight = 1f/rb * cw
binding.fragPhotoEditDraw.layoutParams = RelativeLayout.LayoutParams(bmWidth, bmHeight.toInt())
}
you'll have to pre-scale that drawable before you use it as a background
You can use one of following:
android:gravity="fill_horizontal|clip_vertical"
Or
android:gravity="fill_vertical|clip_horizontal"