I have a graph view (custom view) in Android and a main view. Depending on the user's preference I want to add 1-5 graph views onto my main view but am not quite sure how to. (I'm using purely Java and not xml). I was reading that I might have to use a Relative Layout or something in order to stack views.
Any advice or suggestions are welcome
in your activity you probably have something like this towards the begining of your onCreate() method:
setContentView(R.layout.main);
inside your main.xml file you probably have an element that is some kind of layout. I will assume LinearLayout for now, but it works similarly with all types. You'll need to get a reference to this layout and to do that it must have an id. So if that layout does not have something like this in it you need to add it:
android:id="#+id/myMainLayout"
Then back in your java sometime after you've called setContentView() you can find the reference to your layout with something like this:
LinearLayout myLayout = (LinearLayout)findViewById(R.id.myMainLayout);
Once you have a reference to your layout you can add your graph views to it with something like this:
myLayout.addView(graph1);
myLayout.addView(graph2);
//etc...
If you want to skip the xml layout all together you are allowed to make your layout in java. To do that it would like this:
LinearLayout myLayout = new LinearLayout(YourActivity.this);
myLayout.addView(graph1);
myLayout.addView(graph2);
setContentView(myLayout);
Note that you can only call setContentView() once so you'll need to pass some kind of Layout to it if you want to add more than 1 View.
Edit:
I have never specifically tried but I would think you could call addView() from the constructor in your custom view:
public CustomView() {
this.addView(anotherView);
}
do you have a custom view for your layout too?
Here is an example of a custom view as graph. One needs to have a LinearLayout somewhere in the layout which has ID set to #+id/ll and size of the graph:
public class RootActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
int[] graphData = {3,5,2,7,4,8,1,5,9};
LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
GraphView graphView = new GraphView(this);
ll.addView(graphView);
//call this method with every new set of data
graphView.drawGraph(graphData);
}
class GraphView extends View{
int[] graphData;
Paint graphPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
int screenH;
int screenW;
int colW;
int colH;
int columnCount;
public GraphView(Context context) {
super(context);
graphPaint.setColor(Color.MAGENTA);
graphPaint.setStyle(Style.FILL);
}
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
screenW = w;
screenH = h;
}
public void drawGraph(int[] graphData){
this.graphData = graphData;
columnCount = graphData.length;
invalidate();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
colW = (screenW - 10) / columnCount;
int graphStep = 20;
int columnSpace = 5;
canvas.drawText("GRAPH", 10, 10, graphPaint);
for (int i= 0 ; i < columnCount; i++){
//draw columns from bottom up
canvas.drawRect(
new Rect(
i * colW + 5,
screenH - 5 - (graphData[i] * graphStep),
i * colW + 5 + colW - columnSpace,
screenH - 5
),
graphPaint);
}
}
Related
I have created a custom view that only extends the View class. The custom view works perfectly, except when being used inside a RecyclerView. This is the custom view:
public class KdaBar extends View {
private int mKillCount, mDeathCount, mAssistCount;
private int mKillColor, mDeathColor, mAssistColor;
private int mViewWidth, mViewHeight;
private Paint mKillBarPaint, mDeathBarPaint, mAssistBarPaint, mBgPaint;
private float mKillPart, mDeathPart, mAssistPart;
public KdaBar(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.KdaBar,
0, 0);
try {
mKillCount = a.getInt(R.styleable.KdaBar_killCount, 0);
mDeathCount = a.getInt(R.styleable.KdaBar_deathCount, 0);
mAssistCount = a.getInt(R.styleable.KdaBar_assistCount, 0);
mKillColor = a.getColor(R.styleable.KdaBar_killBarColor, ContextCompat.getColor(getContext(), R.color.kill_score_color));
mDeathColor = a.getColor(R.styleable.KdaBar_deathBarColor, ContextCompat.getColor(getContext(), R.color.death_score_color));
mAssistColor = a.getColor(R.styleable.KdaBar_assistBarColor, ContextCompat.getColor(getContext(), R.color.assist_score_color));
} finally {
a.recycle();
}
init();
}
public void setValues(int killCount, int deathCount, int assistCount) {
mKillCount = killCount;
mDeathCount = deathCount;
mAssistCount = assistCount;
invalidate();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0f, 0f, mViewWidth, mViewHeight, mBgPaint);
canvas.drawRect(mKillPart+mDeathPart, 0f, mKillPart+mDeathPart+mAssistPart, mViewHeight, mAssistBarPaint);
canvas.drawRect(mKillPart, 0f, mKillPart+mDeathPart, mViewHeight, mDeathBarPaint);
canvas.drawRect(0f, 0f, mKillPart, mViewHeight, mKillBarPaint);
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){
super.onSizeChanged(xNew, yNew, xOld, yOld);
mViewWidth = xNew;
mViewHeight = yNew;
float total = mKillCount + mDeathCount + mAssistCount;
mKillPart = (mKillCount/total) * mViewWidth;
mDeathPart = (mDeathCount/total) * mViewWidth;
mAssistPart = (mAssistCount/total) * mViewWidth;
}
private void init() {
mKillBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mKillBarPaint.setColor(mKillColor);
mDeathBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDeathBarPaint.setColor(mDeathColor);
mAssistBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mAssistBarPaint.setColor(mAssistColor);
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setColor(ContextCompat.getColor(getContext(), R.color.transparent));
}
}
The linked image is what the custom view currently looks like (The custom view is the rectangle above the numbers at the center) http://imgur.com/a/Ib5Yl
The numbers below that bar represents their value (They are color-coded in case you haven't noticed). It is obvious that a value of zero on the first item shouldn't show a blue bar on the custom view. Weird, I know.
The method below is where the values are set (it is inside the RecyclerView.Adapter<>):
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
MatchHistory.Match item = mDataset.get(position);
MatchHistory.MatchPlayer[] players = item.getPlayers();
for(MatchHistory.MatchPlayer player: players) {
int steamId32 = (int) Long.parseLong(mCurrentPlayer.getSteamId());
if (steamId32 == player.getAccountId()) {
mCurrentMatchPlayer = player;
}
}
...
holder.mKdaBar.setValues(mCurrentMatchPlayer.getKills(), mCurrentMatchPlayer.getDeaths(), mCurrentMatchPlayer.getAssists());
...
}
This is the onCreateViewHolder:
#Override
public MatchesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_match_item, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
and the ViewHolder class:
public static class ViewHolder extends RecyclerView.ViewHolder {
KdaBar mKdaBar;
public ViewHolder(View v) {
super(v);
...
mKdaBar = (KdaBar) v.findViewById(R.id.kda_bar);
...
}
}
I think it is useful to note that the dataset being used by the adapter changes the position of the items from time to time (since it is being fetched all at the same time but are inserted so that the dataset is ordered). I almost forgot that I also tested not changing the positions of the items inside the dataset, but still there aren't any good results. If you checked the image, you can see that there are other info inside the items and I am 100% sure those are all correct with the exception of the data in the custom view.
I am thinking that I am forgetting some methods that must be overridden but I already saw a lot of tutorials and none of them mentioned about this issue. Looking forward to solving this issue. TIA!
It is pretty hard to tell what is going on exactly especially if this code is working elsewhere, but I'll take a couple guesses.
The main things I noticed:
Comparing int from long where numbers are dangerously close to max
Calling Invalidate from a View inside a RecyclerView (especially onBindView)
Issue 1
In your picture, I'm guessing you are the steamId which are the numbers on the bottom left corner of each RecyclerView's view holder, for example: '2563966339'. You should know that "usually" in Android, Integer.MAX_VALUE = 2147483647. This pretty much means you should use long or things won't be equal when you think they are... (so maybe the boxes are being drawn correctly, but you just don't think the steamId at position 0 is the guy you think?!?!).
(If you want to learn more about it just looked up signed vs usigned bytes for int and long).
So you might have to change some code, but I recommend using long or Long. Two of Many Possibilities Below
Example 1
long steamId32 = Long.parseLong(mCurrentPlayer.getSteamId());
if (steamId32 == player.getAccountId()) {
mCurrentMatchPlayer = player;
}
Example 2
Long steamId32 = mCurrentPlayer.getSteamId();
if (steamId32.equals(player.getAccountId()) {
mCurrentMatchPlayer = player;
}
Issue 2:
A lack of understanding of how RecyclerView works might be causing some problems. In onBindView, you should setup and draw the view as much as possible (without calling invalidate()). This is because RecyclerView is meant to handle all 'recycling'. So you invalidate() call might be causing some strange problems.
I know that onDraw() isn't normally called every time a view is bound, but only upon creation with RecyclerView. This would explain why it worked elsewhere!
Summary and Analysis:
Number 1:
I would call (inside onBindView before setValues)
Log.d("Whatever", "At position: " + position + " we have " + <steamId> + <kills> + <other desired info>).
After you scroll up and down you will see the person on the top and what values are being called and see if it is a problem mentioned in #1 or a problem with your position. If the person should have 0, then let position 0 show 0 kills.
This could also point out one of these problems that I didn't think were as likely, but definitely possible:
I still don't know what mCurrentPlayer is exactly which could cause a problem. Also, if you need to update a 'item' in the adapter simply call mAdapter.updateItemAt(position) from the Activity/Fragment with recyclerView. If you have to move it call mAdapter.notifyItemMoved(fromPos, toPos). All these mean that maybe things aren't what you think when onBindView is being called.
Number 2:
I would recommend putting Log statements also in onDraw() to see if you know when it is ACTUALLY being called, and not just expect it after invalidate(). Most likely invaidate() is being queued by the main thread / recycler view until it decides it wantes to call onDraw().
(Because it already created/drew the item in onCreateView())
You might be surprised by what RecyclerView, LayoutManager, and the Adapter do and how they call the view methods. (You might also just want to put Log statements in onBindView and onCreateView to understand the whole process with onDraw()).
Understanding RecyclerView (and it's parts)
Videos to Learn Basics:
RecyclerView ins and outs - Google I/O 2016
RecyclerView Animations and Behind the Scenes (Android Dev Summit
2015)
And for the readers, Android documentation provided this summary:
Adapter: A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set.
Position: The position of a data item within an Adapter.
Index: The index of an attached child view as used in a call to getChildAt(int). Contrast with Position.
Binding: The process of preparing a child view to display data corresponding to a position within the adapter.
Recycle (view): A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction.
Scrap (view): A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty.
Dirty (view): A child view that must be rebound by the adapter before being displayed.
RecyclerView
RecyclerView.ViewHolder
RecyclerView.LayoutManager
RecyclerView.Adapter
The problem is not with the dataset but with my understanding of how RecyclerView works underneath (just as napkinsterror have mentioned in his answer).
This it the revised custom view:
public class KdaBar extends View {
private int mKillCount, mDeathCount, mAssistCount;
private int mKillColor, mDeathColor, mAssistColor;
private int mViewWidth, mViewHeight;
private Paint mKillBarPaint, mDeathBarPaint, mAssistBarPaint, mBgPaint;
private float mKillPart, mDeathPart, mAssistPart;
public KdaBar(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.KdaBar,
0, 0);
try {
mKillCount = a.getInt(R.styleable.KdaBar_killCount, 0);
mDeathCount = a.getInt(R.styleable.KdaBar_deathCount, 0);
mAssistCount = a.getInt(R.styleable.KdaBar_assistCount, 0);
mKillColor = a.getColor(R.styleable.KdaBar_killBarColor, ContextCompat.getColor(getContext(), R.color.kill_score_color));
mDeathColor = a.getColor(R.styleable.KdaBar_deathBarColor, ContextCompat.getColor(getContext(), R.color.death_score_color));
mAssistColor = a.getColor(R.styleable.KdaBar_assistBarColor, ContextCompat.getColor(getContext(), R.color.assist_score_color));
} finally {
a.recycle();
}
init();
}
public void setValues(int killCount, int deathCount, int assistCount) {
mKillCount = killCount;
mDeathCount = deathCount;
mAssistCount = assistCount;
}
private void calculatePartitions() {
float total = mKillCount + mDeathCount + mAssistCount;
mKillPart = (mKillCount/total) * mViewWidth;
mDeathPart = (mDeathCount/total) * mViewWidth;
mAssistPart = (mAssistCount/total) * mViewWidth;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
calculatePartitions();
canvas.drawRect(mKillPart+mDeathPart, 0f, mKillPart+mDeathPart+mAssistPart, mViewHeight, mAssistBarPaint);
canvas.drawRect(mKillPart, 0f, mKillPart+mDeathPart, mViewHeight, mDeathBarPaint);
canvas.drawRect(0f, 0f, mKillPart, mViewHeight, mKillBarPaint);
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){
super.onSizeChanged(xNew, yNew, xOld, yOld);
mViewWidth = xNew;
mViewHeight = yNew;
}
private void init() {
mKillBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mKillBarPaint.setColor(mKillColor);
mDeathBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDeathBarPaint.setColor(mDeathColor);
mAssistBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mAssistBarPaint.setColor(mAssistColor);
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setColor(ContextCompat.getColor(getContext(), R.color.transparent));
}
}
These are the changes I made:
Removed the invalidate() call from inside the setValues() since the onDraw() callback is invoked when the parent adds a view.
Moved the assignment of mKillPart, mDeathPart, and mAssistPart to calculatePartitions() which is, in turn, called inside onDraw(). This is because the values needed for the calculation are asssured to be complete inside onDraw(). This will be explained below.
This is what I've gathered from Mr. napkinsterror's answer:
When the LayoutManager asks the RecyclerView for a view, ultimately, the onBindViewHolder() method is called. Within that method, data is bound to the views, thus setValues() is called.
The view is returned to the LayoutManager, which will then add the item back to the RecyclerView. This event will trigger onSizeChanged() because the dimensions of the view are not known yet. That's where the mViewWidth and mViewHeight are retrieved. At this point, all the necessary values for calculatePartitions() are complete.
onDraw() is also called because the parent just added an item (check this image). calculatePartitions() is called inside onDraw() and the view will be drawn on the canvas without any problem.
The reason I get wrong values before is because I do the calculatePartitions() inside onSizeChanged() which is very, very wrong since mViewWidth and mViewHeight are yet to be known.
I will mark this as the answer but many thanks to mr. napkinsterror for providing resources so that I can research in the right direction. :)
I have written a subclass of View and I have included a method to set a field. The value of the field affects the the size of the View and the contents of the View.
What do I have to write in the set method to ensure that the view will be properly updated with immediate effect? I am asking because when I use my own custom views as part of a layout of a ListView item, I find that when I try to recycle my own views by using the convertView argument of an ArrayAdapter's getView method, sometimes the set method doesn't seem to work.
Here is a contrived example of the kind of thing I am trying to do. I have only just started learning how to extend View, so there may be other problems with this code.
public final class SpotsView extends View {
private final MyActivity activity;
private int numberOfSpots;
public SpotsView(MyActivity activity) {
super(activity);
this.activity = activity;
}
public void setNumberOfSpots(int numberOfSpots) {
this.numberOfSpots = numberOfSpots;
// What do I have to write here?? invalidate()? forceLayout()?
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Resources resources = activity.getResources();
setMeasuredDimension(
(int) (numberOfSpots * resources.getDimension(R.dimen.block_width)),
(int) resources.getDimension(R.dimen.spots_view_height)
);
}
#Override
protected void onDraw(Canvas canvas) {
int height = getHeight();
int width = getWidth();
float radius = activity.getResources().getDimension(R.dimen.network_outer_radius);
for (int i = 0; i < numberOfSpots; i++)
canvas.drawCircle((i + 0.5F) * (width / numberOfSpots), height * 0.5F, radius, activity.spotPaint());
}
}
It seems you have to call
invalidate();
requestLayout();
according to the source code of TextView#setCompoundDrawablePadding(), a method which affects content and size of the view.
I have a custom ListView. inside a custom ListView, I have a TextView & it's look like this
Now, my question is
1) is there any way to check If names is not to wide to fit onto the screen in Android ? I mean I don't want two lines. Like here Wachler is on 2nd line. So, how do I check if all names are wide to fit onto the screen in one line.
//1)
layout_width = yourLayout.getWidth(); //in order to measure the width of the ListView rows container
To find text size:
//2)
Paint paint = new Paint();
Rect bounds = new Rect();
int text_height = 0;
int text_width = 0;
paint.setTypeface(Typeface.DEFAULT);// your preference here
paint.setTextSize(25);// have this the same as your text size
String text = "Some random text";
paint.getTextBounds(text, 0, text.length(), bounds);
text_height = bounds.height();
text_width = bounds.width();
//3)
if(text_width < layout_width){
//..do your magic
}
Source
you can set your custom listview layout's TextView property as android:ellipsize="end" and also set the android:singleLine="true" in your TextView.
The TextView will use an ellipsize when it can not expand to show all of its text. The attribute ellipsized sets the position of the three dots if it is necessary.
I've solved this issue by checking how many lines does the TextView have after onSizeChanged finish, since only then you will have the real value of the number of lines.
First create an interface (you can define it on your desired Fragment/Activity)
public interface OnCustomTextViewSizeChangedListener {
public void SizeChanged(int size);
}
Then create your custom TextView, Override onSizeChanged and create a function to define your listener.
public class CustomTextView extends TextView {
private OnCustomTextViewSizeChangedListener sizeChangeListener;
...
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(w, h, oldw, oldh);
if(this.sizeChangeListener != null){
this.sizeChangeListener.SizeChanged(getLineCount());
}
}
public void setSizeChangeListener( OnCustomTextViewSizeChangedListener listener){
this.sizeChangeListener = listener;
}
}
Then just create your listener on your Fragment/Activity, define it for your desired TextView and your good to go!
OnCustomTextViewSizeChangedListener textViewSizeChanged = new OnCustomTextViewSizeChangedListener() {
#Override
public void SizeChanged(int size) {
if(size == 2){
//Do your stuff here, like changing text size
}
}
}
PS: don't forget to change your layout to use your CustomTextView
Hope it helps!
If you want your text view display in one line then do this-
textView.setMaxLines(1);
but Remember it cuts out the text which not fit in textview
I suppose you have a TextView displaying the text?
You can check if a textviews text is trimmed by calculating the width of TextView and also calculate the width of text which will be displayed in the textview:
#Override
public void onWindowFocusChanged(boolean hasFocus) {
// TODO Auto-generated method stub
super.onWindowFocusChanged(hasFocus);
isMarqueed("I am fine here. How r u", textView.getWidth(), textView);
isMarqueed("I am fine", textView.getWidth(), textView);
}
private boolean isMarqueed(String text, int textWidth, TextView tv) {
Paint testPaint = new Paint();
testPaint.set(tv.getPaint());
boolean isMarquee = true;
if (textWidth > 0) {
int availableWidth = (int) (textWidth - tv.getPaddingLeft() - tv.getPaddingRight()-testPaint.measureText(text));
System.out.println("...available width..."+availableWidth);
isMarquee = false;
}
return isMarquee;
}
Taken from here: How to check if a TextView String has been trimmed (marquee)?
Since you're using a TextView you can use it's method getLineCount() to determine how many lines this TextView expands over. But be careful when using this method, you must be sure that the view has been drawn before invoking this method. See documentation for more information.
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TableView tv = new TableView(this);
tv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
setContentView(tv);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
public class TableView extends ViewGroup {
private Paint oval;
private RectF rect;
public TableView(Context context) {
super(context);
oval= new Paint(Paint.ANTI_ALIAS_FLAG);
oval.setColor(Color.GREEN);
}
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawOval(rect , oval);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wspec = MeasureSpec.makeMeasureSpec(
getMeasuredWidth(), MeasureSpec.EXACTLY);
int hspec = MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY);
for(int i=0; i<getChildCount(); i++){
View v = getChildAt(i);
v.measure(wspec, hspec);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
float w=r-l;
float h=b-t;
rect=new RectF(w/8,h/8,7*w/8,7*h/8);
float theta = (float) (2 * Math.PI / getChildCount());
for(int i=0; i< getChildCount(); i++) {
View v = getChildAt(i);
w = (rect.right-rect.left)/2;
h = (rect.bottom-rect.top)/2;
float half = Math.max(w, h)/2;
float centerX = rect.centerX()+w*FloatMath.cos(theta);
float centerY = rect.centerY()+h*FloatMath.sin(theta);
v.layout((int)(centerX-half),(int)(centerY-half),(int)(centerX+half),(int)(centerY+half));
}
}
}
Well there are almost NONE good and deep tutorials and hardly any piece of data on how to do custom layouts right, so i tried to figure out how its done, what i am trying to implement is a Layout that paints a green oval at the center of the screen, and i want every child of this layout to be layed out around the oval.
you can think of that oval as a poker table that i want the children of this layout to seat around it.
What currently happens by this code is that i get a white app scren with no oval, so i debugged it and saw that onDraw never gets called...
3 questions:
why is onDraw not getting called?
the sdk warns me that i shouldnt allocate new objects within onLayout method, so where should i calculate the RectF so it is ready for the onDraw call to come?
does calling super.onDraw() would make all children paint themselves? or should i explicitly invoke their draw()?
If I got it all wrong and you guys can guide me in the right direction, or have any links to examples or tutorials related to this subject that would be helpful too!
By default, onDraw() isn't called for ViewGroup objects. Instead, you can override dispatchDraw().
Alternatively, you can enable ViewGroup drawing by calling setWillNotDraw(false) in your TableView constructor.
EDIT:
For #2:
- Initialize it in the constructor, then just call rect.set() in your onLayout() method.
For #3:
- Yes, as far as I'm aware the super call will handle it, you shouldn't have to handle that unless you need to customize the way the children are drawn.
If you want that the canvas is be re-draw call invalidate(), and the method onDraw() will be re-executed
I'm wondering how to measure the dimensions of a view. In my case it is aan Absolute Layout. I've read the answers concerning those questions but I still don't get it.
This is my code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
AbsoluteLayout layoutbase = (AbsoluteLayout) findViewById(R.id.layoutbase);
drawOval();
}
public void drawOval(){ //, int screenWidth, int screenHeight){
AbsoluteLayout layoutbase = (AbsoluteLayout) findViewById(R.id.layoutbase);
int screenWidth = layoutbase.getWidth();
int screenHeight = layoutbase.getHeight();
Log.i("MyActivity", "screenWidth: " + screenWidth + ", screenHeight: " +screenHeight);
Coordinates c = new Coordinates(BUTTONSIZE,screenWidth,screenHeight);
...some code ...
((ViewGroup) layoutbase ).addView(mybutton, new AbsoluteLayout.LayoutParams(BUTTONSIZE, BUTTONSIZE, c.mX, c.mY));
mybutton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
showText(mybutton);
}
});
}
public void showText(View button){
int x = findViewById(LAYOUT).getWidth();
int y = findViewById(LAYOUT).getHeight();
Toast message = Toast.makeText(this, "x: " + x , Toast.LENGTH_SHORT);
message.show();
}
The getWidth() command works great in showText() but it does not in drawOval(). I know it looks a bit different there but I also used the int x = findViewById(LAYOUT).getWidth(); version in drawOval(), and x/y are always 0. I don't really understand why there seems to be no width/height at that earlier point. Even if I actually draw a Button on the Absolute Layout, getWidth() returns 0. Oviously I want to measure the sizes in drawOval().
I think will help you.
LinearLayout headerLayout = (LinearLayout)findviewbyid(R.id.headerLayout);
ViewTreeObserver observer = headerLayout .getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
int headerLayoutHeight= headerLayout.getHeight();
int headerLayoutWidth = headerLayout.getWidth();
headerLayout .getViewTreeObserver().removeGlobalOnLayoutListener(
this);
}
});
}
getWidth() is giving you 0 because onCreate is called before layout actually happens. Due to views being able to have dynamic positions and sizes based on attributes or other elements (fill_parent for example) there's not a fixed size for any given view or layout. At runtime there is a point in time (actually it can happen repeatedly depending on many factors) where everything is actually measured and laid out. If you really need the height and width, you'll have to get them later as you've discovered.
This specially deal with Dimensions so
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
This may help you in managing dimensions.
Note: This returns the display dimensions in pixels - as expected. But the getWidth() and getHeight() methods are deprecated. Instead you can use:
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
as also Martin Koubek suggested.
If your goal is to simply draw an oval on the screen, then consider creating your own custom View rather than messing around with AbsoluteLayout. Your custom View must override onDraw(android.graphics.Canvas), which will be called when the view should render its content.
Here is some extremely simple sample code that might help get you started:
public class MainActivity extends Activity {
private final Paint mPaint = new Paint();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
// create a nested custom view class that can draw an oval. if the
// "SampleView" is not specific to the Activity, put the class in
// a new file called "SampleView.java" and make the class public
// and non-static so that other Activities can use it.
private static class SampleView extends View {
public SampleView(Context context) {
super(context);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.CYAN);
// smoothen edges
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4.5f);
// set alpha value (opacity)
mPaint.setAlpha(0x80);
// draw oval on canvas
canvas.drawOval(new RectF(50, 50, 20, 40), mPaint);
}
}
}
This give you screen resolution:
WindowManager wm = (WindowManager)context.getSystemService(context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point outSize = new Point();
display.getSize(outSize);
kabuko's answer is correct, but could be a little more clear, so let me clarify.
getWidth() and getHeight() are (correctly) giving you 0 because they have not been drawn in the layout when you call them. try calling the two methods on the button after addView() (after the view has been drawn and is present in the layout) and see if that gives you the expected result.
See this post for more information.