I'm trying to add multiple customo views (for now they are simple rectangles) using this code
//Defining the layout
HandLayout = new LinearLayout(this);
HandLayout.setOrientation(LinearLayout.HORIZONTAL);
HandLayout.setBackgroundColor(getResources().getColor(R.color.bg_hand));
HandLayout.setLayoutParams(new LayoutParams(getResources().getInteger(R.integer.dim_hand_width), getResources().getInteger(R.integer.dim_hand_height)));
//Some more code
int dx = getResources().getInteger(R.integer.dim_cardslot_width);
for (int i = 0; i < 6; i++){
Card c = new Card(this,i);
HandLayout.addView(c);
c.setX(i*dx);
}
The problem is that instead of getting 6 rectagles one next to the other I only see the first rectangle.
I think the rectangles are there but are "behind" the first drawn rectangle. How do I tell the view to "move" them dx to the right?
Thanks for any help
EDIT: The problem is with the Card Class code, I think. After a suggestion from one of the user I've tried adding TextViews and they worked. Here is the code for the card class (NOTE the only code not present are the declarations of a bunch of constant ints).
public Card(Context context, int id) {
super(context);
// TODO Auto-generated constructor stub
borders = new Rect(0,0,
getResources().getInteger(R.integer.dim_cardslot_width),
getResources().getInteger(R.integer.dim_cardslot_height));
offw = (getResources().getInteger(R.integer.dim_cardslot_width) - getResources().getInteger(R.integer.dim_card_width))/2;
offh = (getResources().getInteger(R.integer.dim_cardslot_height) - getResources().getInteger(R.integer.dim_card_height))/2;
cborders = new RectF((float)offw, (float)offh,
(float)getResources().getInteger(R.integer.dim_card_width),
(float)getResources().getInteger(R.integer.dim_card_height));
ntext = Typeface.create(Typeface.MONOSPACE,Typeface.NORMAL);
paint = new Paint();
}
protected void onDraw(Canvas canvas){
paint.setColor(getResources().getColor(R.color.bg_card));
canvas.drawRect(borders, paint);
paint.setColor(getResources().getColor(R.color.main_card));
canvas.drawRoundRect(cborders, rad, rad, paint);
paint.setColor(getResources().getColor(R.color.card_text));
paint.setTextSize(14);
canvas.drawText("Card: " + String.valueOf(ID),offw,TopOffset+CharHeight,paint);
}
They are not "behind", they are "next" to each other, the width of every view i think is fill_parent this is why you get only one view, try to put your parent layout inside a scrollView and check the difference
XML solution:
Let's say your card design is represented as follows
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
in your java code:
for (int i = 0; i < 6; i++){
View cardView = LayoutInflater.from(getActivity()).inflate(R.layout.xml_above, null);
TextView tv = cardView.findViewById(R.id.value));
tv.setText("your text");
//if you have click listeners
tv.setOnClickListener(...);
HandLayout.addView(cardView);
}
as simple as it looks
Related
I'm stuck on a strange little problem.
The goal with this activity is to display two texts, one is the original the second the answer text. The answer text contains errors and the user has to find and mark those errors.
The solution we came up with is to split the text into its words and display each word as its own in a TextView. All these TextViews are created dynamically at runtime, because there are many different texts to display.
There are two instances, where we need a 'line break': a) the text contains a linebreak () and b) the width of the display wouldn't fit any more text.
This solution works most of the time but each text has 2-4 words, which don't fit the line width and are therefore broken up into multiple lines visually.
Here's the code:
String[] questionSplit = exercise.exerciseQuestion.split(" ");
ids = new Integer[questionSplit.length];
int displayWidth = getResources().getDisplayMetrics().widthPixels;
int currentLineWidth = 0;
Integer lastIdInRow = 0;
int counter = 0;
for(String bit : questionSplit) {
TextView tv = new TextView(this);
tv.setId(generateViewId());
ids[counter] = tv.getId();
//Exception for <br>
if(bit.equals("<br>")) {
lastIdInRow = ids[counter - 1];
currentLineWidth = 0;
} else {
tv.setText(bit);
tv.setPadding(dpToPx(3), dpToPx(3), dpToPx(2), dpToPx(2));
tv.measure(0, 0);
currentLineWidth += tv.getMeasuredWidth();
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
if(currentLineWidth <= displayWidth && counter == 0) {
// move along, nothing to see here
} else if(currentLineWidth <= displayWidth && counter != 0) {
p.addRule(RelativeLayout.RIGHT_OF, ids[counter - 1]);
} else {
lastIdInRow = ids[counter - 1];
currentLineWidth = 0;
}
if(lastIdInRow != 0 && lastIdInRow != tv.getId()) {
p.addRule(RelativeLayout.BELOW, lastIdInRow);
}
rlTextComparisonOriginal.addView(tv,p);
}
counter++;
}
To explain the layout rules of the TextViews: if the measuredWidth fits into the line, a RIGHT_OF the last id rule is added. If it would overflow, a BELOW the last id in the line rule is added.
As I mentioned earlier, for most of the text that works perfectly. But there are some words which do not fit. If I change the displayWidth to be only 80% of the display width, the error persists just the word changes, so I think it's not the specific text / word.
And here is the relevant part of the view's xml
<ScrollView
android:id="#+id/svTextComparisonDesc"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#id/tvTextComparisonHeaderMiddle"
android:paddingBottom="55dp"
>
<RelativeLayout
android:id="#+id/rlTextComparison"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tvTextComparisonDescription"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#color/text_background"
android:padding="10dp"
android:text="#string/text_exercise_desription"
android:textSize="#dimen/activity_text_description_size"
android:scrollHorizontally="false"
/>
<RelativeLayout
android:id="#+id/rlTextComparisonOriginal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#color/text_background"
android:layout_below="#id/tvTextComparisonDescription"
/>
<RelativeLayout
android:id="#+id/rlTextComparisonAnswer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#id/rlTextComparisonOriginal"
/>
</RelativeLayout>
</ScrollView>
The last bit of information: right now the code above resides in the activity's onCreate Method. If I log the measuredWidth and displayWidth and currentWidth, the logic isn't broken, the measuredWidth fits into the line, but after rendering, it doesn't.
Any ideas what the problem actually might be? Thanks in advance!
After using the mentioned lib the code is much cleaner and looks like this:
//Exception for <br>
if(bit.equals("<br>")) {
FlowLayout.LayoutParams lp = new FlowLayout.LayoutParams(0,0);
lp.setNewLine(true);
flTextComparisonOriginal.addView(tv,lp);
} else {
tv.setText(bit);
tv.setPadding(dpToPx(3), dpToPx(3), dpToPx(2), dpToPx(2));
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
flTextComparisonOriginal.addView(tv, p);
}
In case using a library is acceptable instead of coding it yourself, you will find two projects on github under the keyword "FlowLayout". These sound like they solve the layout you need:
https://github.com/ApmeM/android-flowlayout
https://github.com/blazsolar/FlowLayout
To get width and height of the text view,
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text,0,text.length(),bounds);
int height = bounds.height();
int width = bounds.width();
I have an ImageViewArray and I am randomizing their locations, I want to check if the current location already contains an ImageView without keeping a lot of values of x,y for every ImageView in the array.
Here is my code:
ImageView[] imageViewArray = new ImageView[40];
for (int i = 0; i < 40; i++) {
imageViewArray[i] = new ImageView(this);
imageViewArray[i].setTag(i);
imageViewArray[i].setImageResource(R.mipmap.enemy);
rlt.addView(imageViewArray[i]);
imageViewArray[i].setX(rand.nextInt(rlt.getWidth()));
imageViewArray[i].setY(rand.nextInt(rlt.getHeight()));
if(imageViewArray[i].getX()=) // here I want to check if it already contains an ImageView.
}
Possible Solution
Creating IntArray and adding X value to it and also every Y value for it, then compare between them, is it the best solution?
Problem with the solution - nothing happens, the imageview doesn't change the place and the Toast is not executed.
code:
ImageView[] imageViewArray = new ImageView[20];
ArrayList<Float> xarray = new ArrayList<>();
ArrayList<Float> yarray = new ArrayList<>();
for (int i = 0; i < 20; i++) {
imageViewArray[i] = new ImageView(this);
imageViewArray[i].setTag(i);
imageViewArray[i].setImageResource(R.mipmap.enemy);
imageViewArray[i].setX((float)rand.nextInt(1 + layoutwidth));
imageViewArray[i].setY((float)rand.nextInt(1 + layoutheight));
xarray.add(imageViewArray[i].getX());
yarray.add(imageViewArray[i].getY());
rlt.addView(imageViewArray[i]);
Toast.makeText(MainActivity.this,imageViewArray[i].getX() +"blabla",Toast.LENGTH_LONG);
}
EDIT
layoutwidth is zero :
private int layoutwidth, layoutheight, randx, randy;
private RelativeLayout rlt;
....
rlt = (RelativeLayout) findViewById(R.id.layout);
rlt.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
rlt.getViewTreeObserver().removeOnGlobalLayoutListener(this);
layoutwidth = rlt.getWidth();
layoutheight = rlt.getHeight();
}
});
Yes, that is the best solution. You need to load the rectangles from somewhere. You might merge rectangles if one contains the other, but then you would over-complicate your task and in your quest of writing a more performant and a clearer code, you would end up with a slow and complicated code. Write your code with storing pairs of X, Y points where X is the let-top corner position and Y is the right-bottom corner position.
Note, that I have assumed that the pictures are not rotated. If the images might be rotated, then you need a more general solution, using the inequalities defining the rectangles to see where a point set of a rectangle intersects the point set of the other rectangle. If the intersection is empty set, then the "space is not used up".
I have an Android related issue:
I am trying to centre a logo on the screen of my device, but it won't position correctly.
I am using the following function:
public void ImageCentered(int ID){
ImageView iv = (ImageView) findViewById(ID);
int x = 0;
int y = 0;
RelativeLayout.LayoutParams position = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
x = (screenWidth/2)-(iv.getWidth()/2);
y = (screenHeight/2)-(iv.getHeight()/2);
position.setMargins(x, y, 0, 0);
iv.setLayoutParams(position);
}
This could should work,but it won't. The image is set off slightly to the right and bottom like in this image:
https://www.dropbox.com/s/tf7r1u1xqcmb9t9/2014-08-16%2018.36.04.png
Now, the strange thing is, when I use the following code:
public void ImageCentered(int ID){
ImageView iv = (ImageView) findViewById(ID);
int x = 0;
int y = 0;
RelativeLayout.LayoutParams position = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
x = (screenWidth/2)-(iv.getWidth()/2);
y = (screenHeight/2)-(iv.getHeight()/2);
position.setMargins(x, y, 0, 0);
Message(IntToStr(x)+", "+IntToStr(y));
iv.setLayoutParams(position);
}
this is the result:
https://www.dropbox.com/s/mjw80zlzkav6dzs/2014-08-16%2018.41.16.png
Side note: The text in the Message() function does not matter, nor does its position within the ImageCentered() function.
I am not calling the function in my OnCreate(), as the width and height of the image would always return 0, so I looked something up and found this:
#Override
public void onWindowFocusChanged(boolean hasFocus){
ImageCentered(R.id.image);
}
This piece of code is in my MainActivity.java file, whereas the ImageCentered() function is in my UtilLib.java file.
So, I was wondering: What's going on here? Why does the code work when I pop in a Message() but not when I leave it out?
Sure, I can try hardcoding the data, but what about smaller/bigger screens?
I hope an Android guru can help me out here, as I've been struggling with this for quite some time now.
EDIT
Just noticed something interesting when pressing "OK" on my Message:
https://www.dropbox.com/s/a7lu6588hy1opw7/2014-08-16%2018.51.55.png
My guess is that my problem lies there, but after clicking the "OK" button once more, the data is "492, 207" again. scratches head
Assuming rom your code that the ImageView is inside a RelativeLayout, You could also do:
// get imageview layout params or create new ones
RelativeLayout.LayoutParams params = imageView.getLayoutParams();
params.addRule(RelativeLayout.CENTER_HORIZONTAL);
this way the image will be automatically centered without all those manual calculations!.
You can also specify it in the XML with
<ImageView .....
android:layout_centerHorizontal="true"/>
I've ultimately decided to just hardcode the x and y coordinates, and later on use some sort of scaling-conversion to position them properly, unless someone can provide me with a better method of fixing this.
UPDATE
So, after googling after a while (again), I have finally found the answer and created two functions:
public int GetImageHeight(int ID){
ImageView iv = (ImageView)findViewById(ID);
iv.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
iv.layout(0, 0, iv.getMeasuredWidth(), iv.getMeasuredHeight());
return iv.getMeasuredHeight();
}
public int GetImageWidth(int ID){
ImageView iv = (ImageView)findViewById(ID);
iv.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
iv.layout(0, 0, iv.getMeasuredWidth(), iv.getMeasuredHeight());
return iv.getMeasuredWidth();
}
All you have to do is pass the ID of the image you made in your xml file, like so:
GetImageHeight(R.id.logo)
I'm trying to make a dynamic grid layout, it being API 10+ is the part that's been making it slow going. I tried to make it wrap automatically.. but in the end found it easier just to try to force it into a grid pattern using coordinates. This script was working by itself when I did the positioning at time of creation, but now I am trying to loop through each item as a sort. So if one item is deleted, they all float back into a grid without a hole in the middle.
Problem is, it seems the layout parameters are only applying to the last object.
Here's some base variables and onCreate setup:
int screenWidth;
int screenHeight;
int distStep = 130;
int leftPad = 20;
int numCols;
int baseID = 0;
android.util.DisplayMetrics metrics = this.getResources().getDisplayMetrics();
screenWidth = metrics.widthPixels;
screenHeight = metrics.heightPixels;
numCols = (int) (screenWidth - leftPad) / distStep;
int scrRemain = screenWidth - ((numCols * distStep) + leftPad);
distStep += (int) scrRemain / numCols;
Then on to the main function for adding:
public void addObjToLayout() {
RelativeLayout relLay = (RelativeLayout) this.findViewById(R.id.mainWindow);
for(int i = 1; i <= currQuantity; i++){
TextView tv=new TextView(this);
tv.setTextSize(40);
tv.setId(baseID + i);
tv.setPadding(24, 4, 24, 4);
tv.setBackgroundColor(0x110000FF);
tv.setText(String.valueOf(baseID + i)); //Val for debugging
tv.setTextColor(0xFFFFFFFF);
relLay.addView(tv);
}
baseID += currQuantity;
sortLayout();
}
Then the sorting:
public void sortLayout() {
int leftNum = 20;
int topNum = 0;
for(int i = 1; i <= baseID; i++){
TextView tv= (TextView) this.findViewById(baseID);
MarginLayoutParams mp = new MarginLayoutParams(tv.getLayoutParams());
mp.setMargins(leftNum, topNum, 0, 0);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(mp);
tv.setLayoutParams(lp);
leftNum += distStep;
if(leftNum >= distStep * numCols){
leftNum = leftPad;
topNum += distStep;
}
}
}
What I am getting is all the textViews pile up in the top left corner, except the last one which is positioned exactly where it should be. So it seems in my head, the params object isn't applying until the loop ends or something.. but logically I don't see why.
As I said, this worked when I set the params at the get go, problem is mass updating them all at once. I am pretty new to android, so I hope I'm not just doing something stupid.
Thanks for your time
Margin means it will set a gap between the previous view and current view.
When you add view1, view2 and view3 to grid layout and if you remove view2 at some point of time, then the margin for view3 is set according to view1. So, it won't leave empty space in place of view2. Instead of removing view2 at run time, set the background for view2 as null and set the text as empty as below.
textView.setBackground(null);
textView.setText("");
So that the view is still available but looks as deleted.
Started looking into GridView using an extended baseAdapter. Looks promising:
For more (see #2):
http://www.mkyong.com/android/android-gridview-example/
I've made a very simple customView, a gray rectangle with an arbitrary amount of red markings inside the rectangle marked by percentages.
public class DemoView extends View {
private ShapeDrawable mDrawable;
private ArrayList<ShapeDrawable> mMarks;
public DemoView(Context context, int[] marks) {
super(context);
int x = 0;
int y = 0;
int width = 100;
int height = 10;
// Timeline Initially empty
mDrawable = new ShapeDrawable(new RectShape());
mDrawable.getPaint().setColor(Color.GRAY);
mDrawable.setBounds(x, y, x + width, y + height);
// Add marks
if (marks != null && marks.length % 2 == 0) {
mMarks = new ArrayList<ShapeDrawable>(marks.length / 2);
ShapeDrawable mark;
for (int i = 1; i < marks.length; i = i + 2) {
mark = new ShapeDrawable(new RectShape());
mark.getPaint().setColor(Color.RED);
mark.setBounds(x + marks[i - 1], y, x + marks[i], y + height);
mMarks.add(mark);
}
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawable.draw(canvas);
if (mMarks != null)
for (ShapeDrawable mark : mMarks)
mark.draw(canvas);
}
}
However I can't figure out how to make use of the view. Each time I try to add more than one of the view in a linearlayout or relativelayout, I only see one of the views.
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/llayout"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello"
/>
</LinearLayout>
Layout code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout ll = (LinearLayout) findViewById(R.id.llayout);
demoview = new DemoView(this, new int[]{10,15,35,60});
demoview.setId(ID_NUM++);
ll.addView(demoview);
demoview2 = new DemoView(this, new int[]{0,1,3,6});
demoview2.setId(ID_NUM++);
ll.addView(demoview2);
demoview3 = new DemoView(this, new int[]{25,60});
demoview3.setId(ID_NUM++);
ll.addView(demoview3);
demoview4 = new DemoView(this, new int[]{15,60});
demoview4.setId(ID_NUM++);
ll.addView(demoview4);
}
Results in:
Is this the wrong route to take? Am I missing some obvious key to using this view multiple times? If this is not the correct route is there some other method to making a custom shape? Perhaps extending rectShape?
Following Mibollma's advice, I watched the video above, a video from Google I/O 2009 about speeding up your UI.
The information is most definitely still applicable two years later. Not only was I able to speed up all of my ListViews through the use of ViewHolder, I was able to find the answer to my question.
When creating a custom view, two methods must be overriden, the first is listed above: onDraw.
The missing method? onMeasure(). More information can be found here.