I'm trying to display a bunch of text on the screen by placing TextViews inside rows of single-line LinearLayouts. Each word is stored in its own separate TextView, and I want to be able to place as many TextViews as will fit on a single LinearLayout line and detect when I've run out of horizontal space so that I can move to the next line.
The problem I'm facing is that I can't seem to find a way to measure the changing layout sizes as I create the display, because I can't get a reference width using getWidth() on the parent layout, and even after I add the TextViews, I can't seem to control the width.
We had a working version before, but it did everything using on hard-coded numbers based on the number of characters in a TextView at a fixed size. I'm trying to extend the app to work with all text and screen sizes. If this needs to be completely overhauled, I understand - I just want to be able to fill up the screen with an indefinite number of lines of text.
An obvious solution would be to just place all the text inside one TextView, but we need to be able to access each Word/Ponctuation object and its attributes through the displayed TextViews.
// layout_row is the current LinearLayout row I'm adding my TextViews to
// layout is the LinearLayout parent of all layout_rows
// text.text_content is a linked list of Word and Ponctuation objects
// each Word and Ponctuation object has a TextView attribute called view
private void display_views() {
if (text != null)
{
boolean prochainLigneSuivante; // if new line is to follow
int widthSoFar = 0;
int layoutWidth = layout_row.getWidth();
for (Object o : text.text_content) {
if (o instanceof Word ) {
Word w = (Word) o;
Object next = text.next(o);
if (noNeedForSpace(w)) {
// by default all TextViews have
// right padding to simulate spaces
w.view.setPadding(0, 0, 0, 0);
}
layout_row.addView(w.view);
widthSoFar += w.view.getWidth();
// Am I out of space?
prochainLigneSuivante = widthSoFar >= layoutWidth;
if(prochainLigneSuivante) {
layout_row.removeView(w.view);
widthSoFar = 0;
layout_row = new LinearLayout(context);
layout_row.setOrientation(LinearLayout.HORIZONTAL);
layout_row.addView(w.view);
layout_row.setBackgroundColor(Color.BLACK);
layout_row.setLayoutParams(new
LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
layout.addView(layout_row);
}
}
else if (o instanceof Ponctuation) {
Ponctuation p = (Ponctuation) o;
if (p.text.contains("CR")) {
layout_row = new LinearLayout(context);
layout_row.setOrientation(LinearLayout.HORIZONTAL);
layout_row.setLayoutParams(new
LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
widthSoFar = 0;
layout.addView(layout_row);
}
else {
if (p.view.getText().equals(" "))
p.view.setPadding(0, 0, 0, 0);
layout_row.addView(p.view);
if(!p.view.getText().equals(""))
widthSoFar += p.view.getWidth();
}
}
}
}
else {
Log.e("Text", "text est nul");
}
scroll.refreshDrawableState();
transition.startTransition(0);
}
Related
Is it possible to get each line of a multiline text as it will appear in a textview of a fixed width, with a font, size, alignment without actually drawing the textview ?
My Text is stored in a string. I can apply different font, size, alignment for the text. When passing it to my api, I want to split it into a list of Strings just how it was displayed in the app. Is this possible ?
In the view the text is formatted using the following:
TextView textView = new TextView(getContext());
LayoutParams textLayoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (itemWidth > 0) {
textLayoutParams.width = itemWidth;
}
if (itemHeight == -1) {
textLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
}
textLayoutParams.leftMargin = xPosition;
textLayoutParams.topMargin = yPosition;
textView.setLayoutParams(textLayoutParams);
textView.setPaintFlags(Paint.ANTI_ALIAS_FLAG);
font = getFontName(font);
if (!StringUtils.isEmpty(font)) {
CalligraphyUtils.applyFontToTextView(getContext(), textView, CalligraphyConfig.get(), "fonts/" + font);
}
if (!StringUtils.isEmpty(color))
textView.setTextColor(Color.parseColor(color));
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int)size );
if (alignment.equalsIgnoreCase("center")) {
textView.setGravity(Gravity.CENTER);
} else if (alignment.equalsIgnoreCase("left")) {
textView.setGravity(Gravity.START);
} else if (alignment.equalsIgnoreCase("right")) {
textView.setGravity(Gravity.END);
} else if (alignment.equalsIgnoreCase("left_center")) {
textView.setGravity(Gravity.START | Gravity.CENTER);
}
addView(textView);
For multi line strings, it will display on different lines. In a background process, I want to know which substring was displayed on 1st line, which substring was displayed on second line and so on. Is this even possible ?
I'm trying to create a chess board using a GridLayout in Xamarin.Android. Each View in the GridLayout will be an image view depicting a piece (if the square contains a piece). Before adding the image resource, the board displays fine. But as soon as an image is added, it pushes the size of the ImageView to be much larger than desired.
Here is the code I am using. The following method is run for each square on the board and creates the ImageView and returns it to be added to the GridLayout:
public View BuildSquare(Square squareModel)
{
SquareView rtn = new SquareView(this.Activity, squareModel); //SquareView extends ImageView
rtn.UpdateBackgroundColor(); //set square color
if (squareModel.Piece != null)
{
rtn.DrawPiece();
}
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
//set weight of each row and column to ensure equal size
param.RowSpec = GridLayout.InvokeSpec(GridLayout.Undefined, 1f);
param.ColumnSpec = GridLayout.InvokeSpec(GridLayout.Undefined, 1f);
rtn.LayoutParameters = param;
return rtn;
}
and the DrawPiece() method is as follows, which sets the image resource:
public void DrawPiece()
{
if (squareModel.Piece == null) return;
string key = squareModel.Piece.GetPieceNotation();
int resource = -1;
//get the image resource corresponding to the piece on the ssquare
if (AndroidConstants.PieceResources.TryGetValue(key, out resource))
{
this.SetImageResource(resource);
}
this.SetScaleType(ScaleType.FitXy);
}
When I comment out the rtn.DrawPiece() line, the board displays correctly as shown below:
However, when the line is included and the image resource is set, it displays as follows:
What you are seeing here is the top left square on the board hugely inflated to accommodate the image. I want the image to be scaled to fit in the square in the first image above. I have tried with different ScaleTypes but it seems to make no difference. Is this something to do with using the GridLayout? Or is there something else going on here that I'm missing?
I think you need to set the height and width to your SquareView.
For example:
public View BuildSquare(Square squareModel)
{
SquareView rtn = new SquareView(this, squareModel); //SquareView extends ImageView
rtn.UpdateBackgroundColor(); //set square color
if (squareModel.Piece ==0)
{
rtn.DrawPiece();
}
//Set the height and width
GridLayout.LayoutParams param = new GridLayout.LayoutParams(new ViewGroup.LayoutParams(50, 50));
//set weight of each row and column to ensure equal size
param.RowSpec = GridLayout.InvokeSpec(GridLayout.Undefined, 1f);
param.ColumnSpec = GridLayout.InvokeSpec(GridLayout.Undefined, 1f);
rtn.LayoutParameters = param;
return rtn;
}
And the result:
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'm learning how to implement one-page reading style, like in Google Currents, for my app.
The contents(text) are adjusted based on the screen size and there is no scroll, no zoom.
The overflowed text are in the next page. (I knew that it is using ViewPager.)
Here is the screen shoots:
My questions are:
how can I fit(adjust) the contents(text) to the screen?
how can I automatically parse overflowed text to the next screen? (so that the user
will only need to swipe between screens for reading.)
Also, I'm considering to use TextView for the content. (GoogleCurrent used WebView, I think) My app don't need to use WebView. Will it be possible for textview to achieve like that?
I implemented something like this with several TextViews. First of all, I created a ViewPager for swyping between TextViews.
After than I separate long text to several blocks, one per page.
To get text for page I use this function:
TextView tv = new TextView(context); //creating text view
int width = mtv.getWidth() - tv.getPaddingLeft()-tv.getPaddingRight(); //get width for text
int lHeight = tv.getLineHeight(); getting line height
int height = ((View)mtv.getParent()).getHeight() - tv.getPaddingBottom()-tv.getPaddingTop(); // getting height of text
int linesCount = (int)Math.floor((float)height/lHeight); // get line count on page
String tmpText = "";
for (int i =0; i<linesCount; i++)
{
int index = Math.min(mtv.getLayout().getPaint().breakText(text, true, width, new float[0]), text.indexOf('\n')+1); // breaking text to lines. To use this you need mtv to be measured
String t = text.substring(0, index); //getting text for this textview
if (index+1 < text.length() && (text.charAt(index+1)!=' ' || text.charAt(index+1)!='\n') && t.lastIndexOf(" ")>0)
index = t.lastIndexOf(" ")+1; // hack for situation when line starts with " "
text = text.substring(index);//getting text for next iteration
t = t.substring(0, index);
tmpText+=t;
}
//I use spannable string for links and some styles. Recalculating spans for new indexes
SpannableString s = new SpannableString(tmpText);
Object os[] = spanned.getSpans(offset, offset + tmpText.length(), Object.class);
for (Object o: os)
{
int start = spanned.getSpanStart(o)-offset;
int end = spanned.getSpanEnd(o)-offset;
if (start < 0)
start = 0;
if (end>=s.length())
end = s.length()-1;
s.setSpan(o, start, end, spanned.getSpanFlags(o));
}
offset+=tmpText.length();
while (text.startsWith(" ") || text.startsWith("\n"))
{
text = text.substring(1);
offset++;
}
tv.setText(s);
Nad I think, that google uses TextView, not webView. Read about Spanned and Spannable. It's have ability to show links, images, text with different styles in one textView.
I want to add buttons programmatically on the screen and I am getting the value by parsing an API and now I want to display the buttons according to the length of an array. I am doing this but I am only getting the last button displayed, but inside the for loop I'm getting all values correct but displaying only the last button. This is my code:
RelativeLayout relate;
//...
relate = (RelativeLayout)findViewById(R.id.relative);
protected void onPostExecute(Void result) {
if(dialog.isShowing() == true) {
dialog.dismiss();
}
//int width = 100, height =50, x = 10, y = 20;
for (int i =0;i<adapt_obj.city_name_array.length;i++){
b1 = new Button(myref);
b1.setText(adapt_obj.city_name_array[i]);
relate.addView(b1);
//relate.addView(b1, i, new RelativeLayout.LayoutParams(width,height));
//height = height+80;
}
listlocation.setAdapter(adapt_obj);
adapt_obj.notifyDataSetChanged();
}
A RelativeLayout will stack the views you add to it at the top-let corner if you don't specify some placement rules. Your buttons are added to the layout but they are placed one on top of each other and so the only visible is the last one you add. Here are some modification of your for loop:
RelativeLayout relate; relate = (RelativeLayout)findViewById(R.id.relative);
for (int i = 0; i < adapt_obj.city_name_array.length; i++){
Button b1 = new Button(myref);
b1.setId(100 + i);
b1.setText(adapt_obj.city_name_array[i]);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
if (i > 0) {
lp.addRule(RelativeLayout.BELOW, b1.getId() - 1);
}
b1.setLayoutParams(lp);
relate.addView(b1);
}
You mustn't give x and y values in Android.you can add buttom top left right of an item. Also layout parameters you should use wrap_content or fill_parent.
Button button = new Button(this);
button.setText(#"text");
button.setLayoutParams(new LayoutParams(WRAP_CONTENT,WRAP_CONTENT));
layout.addView(button);
I think the problem is with the relative layout. Your buttons might be getting stack on top of each other. Try making the parent a linear layout.