Here you can see what my problem is, I made a custom TextView in Android, to add stroke to some scores. But so far I'm having 2 separated texts instead of one with stroke...
Here is my code:
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/game_end_share_relative_main"
android:layout_width="#dimen/share_width"
android:layout_height="#dimen/share_height"
android:background="#000000" >
<com.sharing.StrokeTextView
android:id="#+id/user_share_points"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3"
android:textColor="#color/primary"
android:layout_marginRight="16dp"
style="#style/SecondaryFontFamily"
android:textSize="70dp" />
And the custom TextView:
public class StrokeTextView extends TextView {
private int mStrokeColor;
private int mStrokeWidth;
private TextPaint mStrokePaint;
public StrokeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
public StrokeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StrokeTextView(Context context) {
super(context);
}
public void setStrokeColor(int color) {
mStrokeColor = color;
}
public void setStrokeWidth(int width) {
mStrokeWidth = width;
}
#Override
protected void onDraw(Canvas canvas) {
if (mStrokePaint == null) {
mStrokePaint = new TextPaint();
}
mStrokePaint.setTextSize(getTextSize());
mStrokePaint.setTypeface(getTypeface());
mStrokePaint.setFlags(getPaintFlags());
mStrokePaint.setStyle(Paint.Style.STROKE);
mStrokePaint.setColor(mStrokeColor);
mStrokePaint.setStrokeJoin(Paint.Join.ROUND);
mStrokePaint.setStrokeCap(Paint.Cap.ROUND);
mStrokePaint.setStrokeWidth(mStrokeWidth);
mStrokePaint.setShadowLayer(2.0f, 5.0f, 5.0f, Color.BLACK);
mStrokePaint.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "fonts/MikadoBlack.otf"));
String text = getText().toString();
canvas.drawText(text, getWidth() - (mStrokePaint.measureText(text) / 2), getBaseline(), mStrokePaint);
super.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "fonts/MikadoBlack.otf"));
super.onDraw(canvas);
}
}
Thanks for your help in advance :)
Jose
This is because the drawText in super class is drawing at a different position than yours. Try setting the content gravity to 'center' using View.setGravity(Gravity.CENTER) this may solve your problem. Also, if you are using padding on the view then you need to factor it in while calculating the origin for drawText method
int hPadding = getPaddingLeft()+getPaddingRight();
int contentAreaWidth = getWidth() - hPadding;
canvas.drawText(text, contentAreaWidth - (mStrokePaint.measureText(text) / 2), getBaseline(), mStrokePaint);
This would help in aligning the stroked text with the normal text drawn in the super class.
Related
I am trying to create the green shape and add text and an image on top of the green shape. Like this example:
I created the this example in photoshop and I tried to use it as an <ImageView> but the image always look blurry and so, I decided to re-create it using .xml
I know how to create a circle, something like this:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="0dp"
android:shape="ring"
android:thicknessRatio="1.9"
android:useLevel="false" >
<solid android:color="#android:color/transparent" />
<stroke
android:width="10dp"
android:color="#android:color/white" />
</shape>
Thanks,
You have several options but if I understand you correctly you would like to have one View which will fill the whole screen, show some text in addition to the picture and last not least have this curved background.
To achieve this, one can create a custom View which extends from ImageView (or as Android Studio recommends it, from android.support.v7.widget.AppCompatImageView). Extending from ImageView means we'll have to take care of the background and the text, handling the picture will be no problem.
IMO it's better to give the custom View a set of parameters and have it draw the background using a Path than to use a ShapeDrawable because this way one can first evaluate the View's bounds and then determine where exactly the curve should be drawn.
First, let's introduce some dimension values in res/values/dimens.xml
<dimen name="clipped_circle_deviation">100dp</dimen>
<dimen name="clipped_circle_padding_top">60dp</dimen>
Then, the Activity layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.customviews.views.ClippedCircleView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/test"
android:paddingTop="#dimen/clipped_circle_padding_top"
android:scaleType="center"/>
</RelativeLayout>
The picture I used for testing:
And what it looks like (I'm sure the text needs some fine tuning but that's another question)
ClippedCircleView.java
public class ClippedCircleView extends android.support.v7.widget.AppCompatImageView {
public static final String TAG = "ClippedCircle";
private static final int INNER_EDGE_WEIGHT = 2;
private static final int OUTER_EDGE_WEIGHT = 3;
private int measuredWidth;
private int measuredHeight;
private Paint innerPaint;
private Paint outerPaint,;
private Paint textPaint;
private Path path;
public ClippedCircleView(Context context) {
super(context);
init();
}
public ClippedCircleView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ClippedCircleView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
setWillNotDraw(false);
path = new Path();
innerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
innerPaint.setColor(Color.GREEN);
innerPaint.setStyle(Paint.Style.FILL);
outerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
outerPaint.setColor(Color.WHITE);
outerPaint.setStyle(Paint.Style.FILL);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(Color.WHITE);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.clipped_circle_textsize));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
measuredWidth = right - left;
measuredHeight = bottom - top;
float innerEdgeLength = INNER_EDGE_WEIGHT/ (OUTER_EDGE_WEIGHT * 1.0f) * measuredHeight;
path.moveTo(0,0);
path.lineTo(0, innerEdgeLength);
float deviation = getResources().getDimensionPixelSize(R.dimen.clipped_circle_deviation);
path.quadTo(measuredWidth*0.5f, innerEdgeLength + deviation, measuredWidth, innerEdgeLength);
path.lineTo(measuredWidth, 0);
path.lineTo(0,0);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, measuredWidth, measuredHeight, outerPaint);
canvas.drawPath(path, innerPaint);
canvas.drawText("Hello!", 32, 80, textPaint);
canvas.drawText("Welcome to", 32, 160, textPaint);
canvas.drawText("My App", 32, 240, textPaint);
super.onDraw(canvas);
}
}
There is no way to directly write text in shape drawable except converting it to bitmap then write the text. If you really want to create it with Drawable, you should do it using Adobe Illustrator and export it as svg. Then it is possible to import svg as Android Vector Drawable (File -> New -> Vector Asset -> Local file -> ...).
My TextViews xml file
<com.example.blabla.HateTextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/hate"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Text" />
Next code
public class HateTextView extends android.support.v7.widget.AppCompatTextView {
Paint mTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private String str = " ";
HateTextView(Context context, AttributeSet attrs){
super(context, attrs);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.HateTextView);
a.recycle();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint.FontMetrics fm = new Paint.FontMetrics();
mTxtPaint.setColor(Color.parseColor("#d00000"));
mTxtPaint.setTextSize(20 * getResources().getDisplayMetrics().density);
mTxtPaint.getFontMetrics(fm);
mTxtPaint.isAntiAlias();
int margin = 5;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(90 - margin, 100 + fm.top - margin,
115 + mTxtPaint.measureText(str) + margin, 100 + fm.bottom + margin,35,35, mTxtPaint);
}
mTxtPaint.setColor(Color.WHITE);
canvas.drawText(str, 100, 100, mTxtPaint);
}
public void setTextHate(String hate) {
str = hate;
}
}
I create something like this:
HateTextView h = itemView.findViewById(R.id.hate);
h.setTextHate("some text..");
h.setRotation(number);
just because of the rotation so many problems. Or rather because of antialias. Simply from xml getPaint.AntiAlia .. does not work.
the problem looks like this
Here I put "match_parent", and if I put "content" of the text that I'm drawing is not visible at all. How do I insert "Text" into my text instead, which I draw.
public void setTextHate(String hate) {
str = hate;
}
after set str = hate, you have to call invalidate(); to call onDraw(Canvas canvas) again.
I need some help adjusting a function to be constantly updating (<- that may not make sense at first but hopefully a little more explanation will make it clear). This is going to be a long post but please bear with me.
I've got an Activity and a Custom Class. The Activity's Java file only contains a reference to the seekbar that's in it and the OnSeekBarChangeListener. The XML contains said seekbar, and a reference? to the Custom Class (not sure if it's actually called a reference). Here's the code for both:
Java:
public class Painting extends Activity
{
static SeekBar curveBar;
SampleView sampleView;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_painting);
curveBar = (SeekBar)findViewById(R.id.curveBar);
sampleView = new SampleView(this);
curveBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (progress >= 50
{
sampleView.updatePath(progress);
}
else if (progress < 50)
{
sampleView.updatePath((-1) * progress);
}
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
Toast.makeText(Painting.this, "Value: " + curveBar.getProgress(), Toast.LENGTH_SHORT).show();
}
});
}
}
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="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.android.postvu.SampleView">
<com.example.android.postvu.SampleView
android:id="#+id/view"
android:layout_height="300dp"
android:layout_width="match_parent"/>
<SeekBar
android:id="#+id/curveBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/view"
android:layout_alignParentBottom="true"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:elevation="50dp"/>
</RelativeLayout>
My Custom Class is what actually draws the canvas with the curve on the top half. In this class I've got 2 constructors:
public SampleView(Context context, AttributeSet attrs)
{
super(context, attrs);
setFocusable(true);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(90);
mPaint.setTypeface(Typeface.SERIF);
mPath = new Path();
makePath(mPath);
mPathPaint = new Paint();
mPathPaint.setAntiAlias(true);
mPathPaint.setColor(Color.rgb(255,0,0));
mPathPaint.setStyle(Paint.Style.STROKE);
}
public SampleView(Context context)
{
super(context);
setFocusable(true);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(90);
mPaint.setTypeface(Typeface.SERIF);
Log.d("LOGCAT", "" + value);
mPath = new Path();
makePath(mPath);
mPathPaint = new Paint();
mPathPaint.setAntiAlias(true);
mPathPaint.setColor(Color.rgb(255,0,0));
mPathPaint.setStyle(Paint.Style.STROKE);
}
AND FINALLY the 2 functions that I'm stuck on. The first is simply being called from the seekbar in the Activity and it's just so that I can get the value of the seekbar at all times while it's being moved.
public void updatePath(int seekbarProgress)
{
value = seekbarProgress * 6;
}
The second function is what draws the curve itself based on a Bezier Curve:
public void makePath(Path p)
{
// p.moveTo(250, -300);
p.moveTo(0,0);
// p.cubicTo(-250, -550, 750, -550, 250, -300);
p.cubicTo(0,-400,600,-400,600,0); //semi-circle?
// p.cubicTo(-600, -400, 600, -400, 0, 0); //as far as the curve probably should allow
// p.cubicTo(0, 0, 0, 0, 620,0); //flat line
}
MY ACTUAL QUESTION
How can I change the makePath function to take the value variable from the updatePath function so that I can make the p.cubicTo look like p.cubicTo(0,-400,600,-400,value,0) considering that I'm calling makePath in both of the constructors?
****EDIT****
The 2 constructors from my Custom Class (and relevant function) now look like this:
public SampleView(Context context, AttributeSet attrs)
{
super(context, attrs);
setFocusable(true);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(90);
mPaint.setTypeface(Typeface.SERIF);
mPath = new Path();
// updatePath(value);
makePath(mPath, value);
mPathPaint = new Paint();
mPathPaint.setAntiAlias(true);
mPathPaint.setColor(Color.rgb(255,0,0));
mPathPaint.setStyle(Paint.Style.STROKE);
}
public SampleView(Context context)
{
super(context);
setFocusable(true);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(90);
mPaint.setTypeface(Typeface.SERIF);
mPath = new Path();
// updatePath(value);
makePath(mPath, value);
Log.d("LOGTAG2", "" + value);
mPathPaint = new Paint();
mPathPaint.setAntiAlias(true);
mPathPaint.setColor(Color.rgb(255,0,0));
mPathPaint.setStyle(Paint.Style.STROKE);
}
public int updatePath(int seekbarProgress)
{
return value = seekbarProgress * 6;
}
public void makePath(Path p, int number)
{
p.moveTo(0,0);
p.cubicTo(0, 0, 0, 0, number,0); //flat line
}
Android Studio is highlighting updatePath and when I hover over it, it tells me Return value of the method is never used. Unless I'm doing something wrong, I am using that value in both constructors where it says makePath(mPath, value);. I'm not sure what else to do at this point because it doesn't make any sense to me. I really want to know 1) how to fix it and 2) what's causing it (why it's telling me that I am not using it (which I think I am)).
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/titleBarBG"
android:layout_alignParentLeft="true" >
<RelativeLayout
android:id="#+id/scrollContent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<GridView
android:id="#+id/issueList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/archiveTitle"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#drawable/customshape"
android:numColumns="3"
android:overScrollMode="never"
android:scrollbars="none" >
</GridView>
</RelativeLayout>
</ScrollView>
I would like to create a gridview that act like a table . For example, the size of the grid will increase that will make the gridview taller. Instead of hide the extra content ,I would like the grid view show all content and expand the height when there is additional content
How to implement this? thanks
public class MyGridView extends GridView {
public MyGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyGridView(Context context) {
super(context);
}
public MyGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
This is slightly cleaned up version of: Grid of images inside ScrollView
WrappedGridView.java:
/**
* Use this class when you want a gridview that doesn't scroll and automatically
* wraps to the height of its contents
*/
public class WrappedGridView extends GridView {
public WrappedGridView(Context context) {
super(context);
}
public WrappedGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WrappedGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
Include in an XML layout like you would a GridLayout. Use an adapter to provide it views.
As far as I can tell, this is the simplest solution available now. There no other view available in the framework that handles wrapping. It would be nice if someone were to provide an elegant, automatically sizing table view. Modifying GridView.java for this purpose may not be a bad idea.
Alternatively, you may find one of the 'FlowLayout' projects acceptable. There is android-flowlayout and FlowLayout. These are a little more flexible than a simple grid and, I assume, a little less efficient. You also shouldn't need to provide them an adapter.
I want to download an image (of unknown size, but which is always roughly square) and display it so that it fills the screen horizontally, and stretches vertically to maintain the aspect ratio of the image, on any screen size. Here is my (non-working) code. It stretches the image horizontally, but not vertically, so it is squashed...
ImageView mainImageView = new ImageView(context);
mainImageView.setImageBitmap(mainImage); //downloaded from server
mainImageView.setScaleType(ScaleType.FIT_XY);
//mainImageView.setAdjustViewBounds(true);
//with this line enabled, just scales image down
addView(mainImageView,new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
I accomplished this with a custom view. Set layout_width="fill_parent" and layout_height="wrap_content", and point it to the appropriate drawable:
public class Banner extends View {
private final Drawable logo;
public Banner(Context context) {
super(context);
logo = context.getResources().getDrawable(R.drawable.banner);
setBackgroundDrawable(logo);
}
public Banner(Context context, AttributeSet attrs) {
super(context, attrs);
logo = context.getResources().getDrawable(R.drawable.banner);
setBackgroundDrawable(logo);
}
public Banner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
logo = context.getResources().getDrawable(R.drawable.banner);
setBackgroundDrawable(logo);
}
#Override protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * logo.getIntrinsicHeight() / logo.getIntrinsicWidth();
setMeasuredDimension(width, height);
}
}
In the end, I generated the dimensions manually, which works great:
DisplayMetrics dm = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels;
int height = width * mainImage.getHeight() / mainImage.getWidth(); //mainImage is the Bitmap I'm drawing
addView(mainImageView,new LinearLayout.LayoutParams(
width, height));
I just read the source code for ImageView and it is basically impossible without using the subclassing solutions in this thread. In ImageView.onMeasure we get to these lines:
// Get the max possible width given our constraints
widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
// Get the max possible height given our constraints
heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
Where h and w are the dimensions of the image, and p* is the padding.
And then:
private int resolveAdjustedSize(int desiredSize, int maxSize,
int measureSpec) {
...
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
/* Parent says we can be as big as we want. Just don't be larger
than max size imposed on ourselves.
*/
result = Math.min(desiredSize, maxSize);
So if you have a layout_height="wrap_content" it will set widthSize = w + pleft + pright, or in other words, the maximum width is equal to the image width.
This means that unless you set an exact size, images are NEVER enlarged. I consider this to be a bug, but good luck getting Google to take notice or fix it. Edit: Eating my own words, I submitted a bug report and they say it has been fixed in a future release!
Another solution
Here is another subclassed workaround, but you should (in theory, I haven't really tested it much!) be able to use it anywhere you ImageView. To use it set layout_width="match_parent", and layout_height="wrap_content". It is quite a lot more general than the accepted solution too. E.g. you can do fit-to-height as well as fit-to-width.
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
// This works around the issue described here: http://stackoverflow.com/a/12675430/265521
public class StretchyImageView extends ImageView
{
public StretchyImageView(Context context)
{
super(context);
}
public StretchyImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public StretchyImageView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Call super() so that resolveUri() is called.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// If there's no drawable we can just use the result from super.
if (getDrawable() == null)
return;
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int w = getDrawable().getIntrinsicWidth();
int h = getDrawable().getIntrinsicHeight();
if (w <= 0)
w = 1;
if (h <= 0)
h = 1;
// Desired aspect ratio of the view's contents (not including padding)
float desiredAspect = (float) w / (float) h;
// We are allowed to change the view's width
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
// We are allowed to change the view's height
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
int pleft = getPaddingLeft();
int pright = getPaddingRight();
int ptop = getPaddingTop();
int pbottom = getPaddingBottom();
// Get the sizes that ImageView decided on.
int widthSize = getMeasuredWidth();
int heightSize = getMeasuredHeight();
if (resizeWidth && !resizeHeight)
{
// Resize the width to the height, maintaining aspect ratio.
int newWidth = (int) (desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright;
setMeasuredDimension(newWidth, heightSize);
}
else if (resizeHeight && !resizeWidth)
{
int newHeight = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom;
setMeasuredDimension(widthSize, newHeight);
}
}
}
Setting adjustViewBounds to true and using a LinearLayout view group worked very well for me. No need to subclass or ask for device metrics:
//NOTE: "this" is a subclass of LinearLayout
ImageView splashImageView = new ImageView(context);
splashImageView.setImageResource(R.drawable.splash);
splashImageView.setAdjustViewBounds(true);
addView(splashImageView);
I've been struggling with this problem in one form or another for AGES, thank you, Thank You, THANK YOU.... :)
I just wanted to point out that you can get a generalizable solution from what Bob Lee's done by just extending View and overriding onMeasure. That way you can use this with any drawable you want, and it won't break if there's no image:
public class CardImageView extends View {
public CardImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public CardImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CardImageView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable bg = getBackground();
if (bg != null) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * bg.getIntrinsicHeight() / bg.getIntrinsicWidth();
setMeasuredDimension(width,height);
}
else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
In some cases this magic formula beautifully solves the problem.
For anyone struggling with this coming from another platform, the "size and shape to fit" option is handled beautifully in Android, but it's hard to find.
You typically want this combination:
width match parent,
height wrap content,
adjustViewBounds turned ON (sic)
scale fitCenter
cropToPadding OFF (sic)
Then it's automatic and amazing.
If you're an iOS dev, it's utterly amazing how simply, in Android, you can do "totally dynamic cell heights" in a table view .. err, I mean ListView. Enjoy.
<com.parse.ParseImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/post_image"
android:src="#drawable/icon_192"
android:layout_margin="0dp"
android:cropToPadding="false"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:background="#eff2eb"/>
I have managed to achieve this using this XML code only. It might be the case that eclipse does not render the height to show it expanding to fit; however, when you actually run this on a device, it properly renders and provides the desired result. (well at least for me)
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="#drawable/whatever" />
</FrameLayout>
I did it with these values within a LinearLayout:
Scale type: fitStart
Layout gravity: fill_horizontal
Layout height: wrap_content
Layout weight: 1
Layout width: fill_parent
Everyone is doing this programmily so I thought this answer would fit perfectly here. This code worked for my in the xml. Im NOT thinking about ratio yet, but still wanted to place this answer if it would help anyone.
android:adjustViewBounds="true"
Cheers..
A very simple solution is to just use the features provided by RelativeLayout.
Here is the xml that makes it possible with standard Android Views:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:id="#+id/button_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentBottom="true"
>
<Button
android:text="button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:text="button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:text="button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<ImageView
android:src="#drawable/cat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_above="#id/button_container"/>
</RelativeLayout>
</ScrollView>
The trick is that you set the ImageView to fill the screen but it has to be above the other layouts. This way you achieve everything you need.
Its simple matter of setting adjustViewBounds="true" and scaleType="fitCenter" in the XML file for the ImageView!
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/image"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
/>
Note: layout_width is set to match_parent
You are setting the ScaleType to ScaleType.FIT_XY. According to the javadocs, this will stretch the image to fit the whole area, changing the aspect ratio if necessary. That would explain the behavior you are seeing.
To get the behavior you want... FIT_CENTER, FIT_START, or FIT_END are close, but if the image is narrower than it is tall, it will not start to fill the width. You could look at how those are implemented though, and you should probably be able to figure out how to adjust it for your purpose.
ScaleType.CENTER_CROP will do what you want: stretch to full width, and scale the height accordingly. if the scaled height exceeds the screen limits, the image will be cropped.
Look there is a far easier solution to your problem:
ImageView imageView;
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
imageView =(ImageView)findViewById(R.id.your_imageView);
Bitmap imageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.your_image);
Point screenSize = new Point();
getWindowManager().getDefaultDisplay().getSize(screenSize);
Bitmap temp = Bitmap.createBitmap(screenSize.x, screenSize.x, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(temp);
canvas.drawBitmap(imageBitmap,null, new Rect(0,0,screenSize.x,screenSize.x), null);
imageView.setImageBitmap(temp);
}
You can use my StretchableImageView preserving the aspect ratio (by width or by height) depending on width and height of drawable:
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
public class StretchableImageView extends ImageView{
public StretchableImageView(Context context) {
super(context);
}
public StretchableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StretchableImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(getDrawable()!=null){
if(getDrawable().getIntrinsicWidth()>=getDrawable().getIntrinsicHeight()){
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * getDrawable().getIntrinsicHeight()
/ getDrawable().getIntrinsicWidth();
setMeasuredDimension(width, height);
}else{
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = height * getDrawable().getIntrinsicWidth()
/ getDrawable().getIntrinsicHeight();
setMeasuredDimension(width, height);
}
}
}
}
For me the android:scaleType="centerCrop" did not resolve my problem. It actually expanded the image way more. So I tried with android:scaleType="fitXY" and It worked excellent.
This working fine as per my requirement
<ImageView
android:id="#+id/imgIssue"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitXY"/>