I'm trying to create a view that has an ImageView with a TextView on top of it.
I'm stuck trying to find any good examples on this so I'm hoping someone can tell me what I've done wrong or where to look for examples. I've seen http://developer.android.com/guide/topics/ui/custom-components.html#compound but only really describes the minimal requirements.
I've found a couple things like Creating Compound Controls With Custom XML Attributes but my setup is a tad different.
My code is below, which extends LinearLayout and am not sure if that is even the way to go. Nothing displays in the layout.
ImageWithOverlay
public class ImageWithOverlay extends LinearLayout {
ImageView image;
TextView text;
public ImageWithOverlay(Context context) {
super(context);
setupDisplay(context);
}
public ImageWithOverlay(Context context, AttributeSet attrs) {
super(context, attrs);
setupDisplay(context);
}
public ImageWithOverlay(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setupDisplay(context);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
image.draw(canvas);
text.draw(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void setupDisplay(Context context) {
image = new ImageView(context);
image.setImageResource(R.drawable.flowers);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
image.setLayoutParams(lp);
text = new TextView(context);
text.setText("testing");
lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
text.setLayoutParams(lp);
}
}
status.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<com.package.blah.ImageWithOverlay
android:id="#+id/imageWithOverlay1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
</com.package.blah.ImageWithOverlay>
</RelativeLayout>
Use a RelativeLayout instead of a LinearLayout, this will allow you to place the text over the image.
The reason why they aren't appearing is that they haven't been added using addView, so call this after you've set each one's LayoutParams.
Remove the onDraw() call, this isn't used for ViewGroups unless you explicitly request it do be. And once the Views are added they'll be drawn automatically.
Related
Intro:
I am attempting to add various Views to my custom RelativeLayout, i.e. Buttons, ImageViews, etc however none of them render/show.
Documentation:
As shown on numerous SO questions: here, here, here, here and many more,
I have the standard requirements for extending a layout, i.e. the 3 constructors, that being:
public RelativeLayout(Context context) {}
public RelativeLayout(Context context, AttributeSet attrs) {}
public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr){}
referred to here on Android Developer site.
Implementation:
My RelativeLayout named DiceRoller has the following implementation:
public class DiceRoller extends RelativeLayout {
private DieContainer dieContainer;
private Context mContext;
private int speed;
private Runnable moveDies;
private Handler handler;
private Timer timer;
public DiceRoller(Context context) {
super(context);
mContext = context;
init();
}
public DiceRoller(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public DiceRoller(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
private void init() {
//source : https://stackoverflow.com/questions/28265286/custom-relative-layout-not-showing-child-views
setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
setGravity(Gravity.CENTER);
ImageView mainImage = new ImageView(mContext);
mainImage.setId(1994);
LayoutParams params = new LayoutParams(100, 100);
mainImage.setImageResource(R.drawable.die1);
mainImage.setLayoutParams(params);
addView(mainImage);
RelativeLayout.LayoutParams crossParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
crossParams.addRule(RelativeLayout.ALIGN_TOP | RelativeLayout.ALIGN_LEFT, mainImage.getId());
ImageView crossImage = new ImageView(mContext);
crossImage.setImageResource(R.drawable.die6);
crossImage.setLayoutParams(crossParams);
addView(crossImage);
TextView tv = new TextView(mContext);
tv.setText("hello world");
addView(tv);
}
}
Please Note: the contents of the init() method was purely to test if views were infact rendered. This was my last attempt at debugging the issue, previously I added views from my MainActivity aswell, obviously without success
With an associated layout file
<?xml version="1.0" encoding="utf-8"?>
<com.myapp.DiceRoller
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="1000px"
android:layout_height="1000px"
android:background="#color/colorAccent"
android:id="#+id/rollerBack">
</com.myapp.DiceRoller>
What is the problem:
The problem is simple. No child of the layout is rendered/shown/visible.
I attempted adding a child in my MainActivity, programmatically. It did not render. I attempted adding a child within this RelativeLayout class, it did not render.
Additional Info:
note: When adding views, I always added text or some image, I also set the X, Y values, also included RelativeLayout.LayoutParams() with the wrap option set.
When debugging this issue, if I added a view (ImageView, Button, etc), the layout has each child stored, and each child's parent is this RelativeLayout. Each child has a width, height, X, Y value and some content (either an image or text), thus the problem does not lie with the children.
I am at a loss, I have no idea why it doesn't render, any help would be greatly appreciated!
I have LinearLayout which contains two Button widgets with layout_weight=1. Depends on length of Button's text or screen resolution I get buttons with rectangular form (gray rectangles) but I need to keep the square form (blue squares).
I was trying to change height of LinearLayout param in onStart() method, depends on Button's width, but getWidth() returns 0. I understand that it's because view at that moment still not rendered. Please, help me to solve my problem.
There are many ways to achieve this. If you need to find the real width of an element, you can:
1) attach an OnGlobalLayoutListener to the view's ViewTreeObserver (but remember to remove it when you are done)
2) or you can manually measure what you need:
if(view.getMeasuredHeight() == 0){
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(metrics);
view.measure( metrics.widthPixels, metrics.heightPixels );
}
int realHeight = view.getMeasuredHeight();
//...your code
You are exactly right because at that time view will not be drawn so for that you have to use viewtreeobserver:
like this:
final ViewTreeObserver treeObserver = viewToMesure
.getViewTreeObserver();
treeObserver
.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
System.out.println(viewToMesure.getHeight()
+ "SDAFASFASDFas");
heith = viewToMesure.getHeight();
}
});
this view tree observer will be called after creating the view based on that you calculate and you can change.
You can use a custom view to set the view's height to be tha same as its width by overriding onMeasure:
public class SquareButton extends Button {
public SquareButton (Context context) {
super(context);
}
public SquareButton (Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareButton (Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
}
}
All you have to is use the custom button in your xml layout and you don't have to do anything in the activity:
<com.package.name.SquareButton
android:layout_with="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
I could not find tips or examples about how to do this. I want to add a progressbar over a Rect that I have already drawn, so how I can I do this?
Your answers will be truly appreciated! :)
Edited
public class MainView extends View {
public MainView(Context context) {
super(context);
}
public MainView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
logo.set(getPX(5), getPX(10), getPX(65), getPX(70));
background.set(getPX(30), getPX(2), canvas.getWidth() - getPX(10),
getPX(81));
canvas.drawRect(background, paint);
canvas.drawRect(logo, paint);
}
final private int getPX(float dp) {
return (int) (getResources().getDisplayMetrics().density * dp);
}
}
Based on your implementation (as you shown it to me):
<ScrollView .. >
<LinearLayout .. >
<MainView .. />
<MainView .. />
<MainView .. />
</LinearLayout>
</ScrollView>
my proposed solution would be - extend your CustomView class with LinearLayout (because then you can add additional views to your custom view):
public class MainView extends LinearLayout {
private Rect logo;
private Rect background;
public MainView(Context context) {
super(context);
setWillNotDraw(false); //needed in order to call onDraw method
logo = new Rect();
background = new Rect();
}
public MainView(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
logo = new Rect();
background = new Rect();
RelativeLayout progressBarLayout = new RelativeLayout(context);
RelativeLayout.LayoutParams lay = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.FILL_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
ProgressBar progressBar = new ProgressBar(context, null, R.attr.progressBarStyleHorizontal);
progressBar.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
progressBar.setIndeterminate(true); //remove that (only for demonstration purposes)
lay.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
progressBarLayout.addView(progressBar, lay);
addView(progressBarLayout);
//Apart from the ProgressBar you are able to add as many views as you want to your custom view and align them as you would like
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
logo.set(getPX(5), getPX(10), getPX(65), getPX(70));
background.set(getPX(30), getPX(2), canvas.getWidth() - getPX(10),
getPX(81));
canvas.drawRect(background, paint);
canvas.drawRect(logo, paint);
}
final private int getPX(float dp) {
return (int) (getResources().getDisplayMetrics().density * dp);
}
}
In this example I am only showing how to add ProgressBar component, because that was your original question
You will need to add some more code in order to carry out your requirements (got from questioner):
P.S. This is just a solution to your particular problem/request. I would advise to use ListView component, which is better in the sense that it reuses views, so in such cases where you will have LOTS OF custom view instances, your application might become unusable, because there will be too much load on the activity class.
In order for you to migrate to using ListView component, try some examples first, like this
I am trying to reduce the line spacing in a TextView by setting a negative 'add' to TextView.setLineSpacing(). It works well except that the bottom line get truncated.
Main layout
<TextView
android:id="#+id/text_view"
android:padding="dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
tools:context=".MainActivity" />
Main activity: (notice the
package com.font_test;
import android.app.Activity;
import android.graphics.Typeface;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/custom_fonts.ttf");
final TextView tv = (TextView) findViewById(R.id.text_view);
tv.setTypeface(typeface);
tv.setTextSize(60);
tv.setLineSpacing(-30f, 1f); // *** -30 to reduce line spacing
tv.setBackgroundColor(0x280000ff);
tv.setText("gggkiiikkk" + "\n" + "gikgikgik" + "\n" + "kigkigkig");
}
}
This results in truncation at the bottom of the view (notice the 'g' at the bottom line):
It seems that the problem is related to incorrect layout measurement. If I set the TextView to
android:layout_height="fill_parent"
It does render properly:
Any idea how to fix it? I don't mind to have ugly workarounds if it helps. I also have access to FontForge and I can modify the font file if needed.
littleFluffyKittys answer is good but it didn't work on some devices if the linespacing was set through xml
I calculate the additional height needed by comparing the original height of the font with the height the textview calculates for a line.
If the line height is smaller than the height of the font the diffrence is added one time.
This works down to at least API 10 propably lower (just not tested any lower)
public class ReducedLineSpacingTextView extends TextView {
public ReducedLineSpacingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ReducedLineSpacingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReducedLineSpacingTextView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int truncatedHeight = getPaint().getFontMetricsInt(null) - getLineHeight();
if (truncatedHeight > 0) {
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + truncatedHeight);
}
}
}
I ran into this same problem but when I was trying to use a spacing multiplier less than 1.
I created a subclass of TextView that fixes the truncation of the last line automatically and doesn't require you set a known/fixed spacing at the bottom.
Just use this class and you can use it normally, you don't need to apply any additional spacing or fixes.
public class ReducedLineSpacingTextView extends TextView {
private boolean mNegativeLineSpacing = false;
public ReducedLineSpacingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ReducedLineSpacingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReducedLineSpacingTextView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mNegativeLineSpacing) { // If you are only supporting Api Level 16 and up, you could use the getLineSpacingExtra() and getLineSpacingMultiplier() methods here to check for a less than 1 spacing instead.
Layout layout = getLayout();
int truncatedHeight = layout.getLineDescent(layout.getLineCount()-1);
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + truncatedHeight);
}
}
#Override
public void setLineSpacing(float add, float mult) {
mNegativeLineSpacing = add < 0 || mult < 1;
super.setLineSpacing(add, mult);
}
}
Nice!
That'll make the job but it's never a good idea to put constants values wherever we have variables. You can use the lineSpacing values to add them to the onMeasure method in a dinamyc way.
Note that this values are always available through "getLineSpacingExtra()" and "getLineSpacingMultiplier()". Or even easier you can get the value of both summed up: "getLineHeight()".
Although it feels for me that this value should be included in the onMeasure method, you can always measure the exact height you need and then make a simple check:
final int measuredHeight = getMeasuredHeight();
if (measuredHeight < neededHeight) {
setMeasuredDimension(getMeasuredWidth, neededHeight);
}
One last thing, you don't need to pass the context along in a separated attribute. If you have a look to your constructors, the context is already there. If you needed along the code of your component you can just use "getContext()".
Hope it helps.
Use this to reduce line spacing in text view
**
android:lineSpacingMultiplier="0.8"
**
If padding doesn't work, margin should do the job. If you still have problem you can always apply the line spacing value to the onMeasure method of the view. You'll have to create a custom component for that and extend onMeasure.
Just add paddingBottom to declaration of your TextView xml, pick a value which produces a good result. And consequently set values for other paddings (top, let and right). This should fix your problem
This is what I did based on Jose's answer here and it seems to work. I am not very familiar with the intricate of the layout mechanism. Is this code safe? Any problem with it?
Layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.font_test.MyTextView
android:id="#+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
tools:context=".MainActivity" />
</RelativeLayout>
Added custom TextView that extends the vertical height by N pixels:
package com.font_test;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
public class MyTextView extends TextView {
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// TODO: provide an API to set extra bottom pixels (50 in this example)
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + 50);
}
}
Result text view rendering without truncation at the bottom:
I have simply extended the ImageView so an image goes full width. Like so:..
public class BannerImageView extends ImageView {
public BannerImageView(Context context) {
super(context);
}
public BannerImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BannerImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * getDrawable().getIntrinsicHeight() / getDrawable().getIntrinsicWidth();
setMeasuredDimension(width, height);
}
}
In XML I have declared it as follows:
<com.whatever.next.BannerImageView
android:id="#+id/banner"
android:src="#+drawable/logo"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
I receive the following errors:
main.xml: Unable to resolve drawable "com.android.layoutlib.bridge.ResourceValue#48537a0f" in attribute "src"
then I get the expected null pointer exceptions.
I am confused as I thought since I am not altering the default behaviour of the ImageView it would show in the graphical layout. I have read through the other similar questions and that confused me some more.
For the record the above code works fine on an actual device.
Any help is appreciated!
instead of
android:src="#+drawable/logo"
use
android:src="#drawable/logo"
No + for the src attribute