Inside CursorAdapter's bindView() I bind data to the following layout:
A TextView and two Buttons : "UP" and "DOWN".
The TextView is defined in XML like so:
<TextView
android:id="#+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="25dp"
android:paddingRight="25dp"
android:paddingTop="25dp"
android:scrollbars="vertical"
android:textAlignment="textStart"
android:textColor="#5c6284"
app:autoSizeMaxTextSize="40sp"
app:autoSizeMinTextSize="20sp"
app:autoSizeTextType="uniform" />
A vertical scrolling behavior is applied to the TextView, which is being controlled by the "UP and "DOWN" Buttons.
I would like to determine if the TextView requires scrolling ( is long enough to not fit its provided drawing area ) so that I can enable/disable the "UP" and "DOWN" buttons accordingly.
I'm currently reading BaseMovementMethod's scrollDown function, thinking of applying its measuring logic to my adapter, though I have the feeling that it should be much simpler. Maybe a built in behavior that I'm not aware of.
Is there a better way to achieve this, other than my suggested approach?
What I would do is put the textview inside a scrollview like so:
<ScrollView
android:id="#+id/scroller"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<TextView
android:id="#+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test texts here"/>
</ScrollView>
In your activity, execute these lines:
boolean needScrolling = false;
if(scroller.getHeight() < tv_content.getHeight()) needScrolling = true;
You can use Static Layout class. If you set it up with your TextView's parameters you'll be able to calculate the height of the rendered text.
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
float spacingMultiplier = 1;
float spacingAddition = 0;
boolean includePadding = false;
StaticLayout myStaticLayout = new StaticLayout(text, myTextView.getPaint(), myTextView.getWidth(), alignment, spacingMultiplier, spacingAddition, includePadding);
float height = myStaticLayout.getHeight();
Then you can compare the height of your text and height of your TextView and figure out if it will require scrolling or not.
You can also try to manually create a Paint object with your min text size if myTextView.getPaint() approach does not work.
Calculate mTextView's height without data and with data and then compare it
mTextView.post(new Runnable() {
#Override
public void run() {
int lineHeight=mTextView.getCompoundPaddingBottom()+ mTextView.getCompoundPaddingTop()+mTextView.getLineHeight();
int height=mTextView.getHeight()-(mTextView.getCompoundPaddingTop()+mTextView.getLineHeight());
if (height>lineHeight){
}
}
});
Related
I have a trouble while creating a kind of view. Let's just say that i'm trying to create something similar to a clock. This means that we have to position numbers in a distance and angle from a center.
I tried to emulate a way to position two elements. A center ImageView and a clock number. In XML I have something similar to:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/lyt_rootView"
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/ivCenter"
android:background="#drawable/cid"/>
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/ivNumber"
android:background="#drawable/add_big"
android:layout_marginLeft="-50dp"
android:layout_marginTop="50dp"/>
</RelativeLayout>
And it works pretty well, the images set as they should be, being completly centered the first image (ivCenter) and taking some margin from the center for positioning the second image.
Now I have to do the same programatically so I can create lots of imageViews simulating the numbers. So I have this code:
RelativeLayout lyt = (RelativeLayout) findViewById(R.id.lyt_rootView);
int avatarSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics());
int distance = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics());
// center image
ImageView ivImageCenter = new ImageView(this);
ivImageCenter.setImageResource(R.drawable.cid);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(avatarSize,avatarSize);
lyt.addView(ivImageCenter, lp);
// first image (simulating a number in a clock)
ImageView ivNumber1 = new ImageView(this);
ivNumber1.setImageResource(R.drawable.add_big);
RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(avatarSize,avatarSize);
lp2.setMargins(distance, distance, 0, 0);
lyt.addView(ivNumber1, lp2);
but the result is not similar as the XML example. I don't see what I'm missing. The result of creating the views programatically is that the two imageViews are trying to position in a center space in the screen making the first image not centered to the screen.
What I'm missing?
Finally I solved it by creating a one pixel layout centered in XML that acts as a reference and programatically adding the views in that reference view.
I've created the following layout xml-file:
<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"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="some.package.MainActivity" >
<TableLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TableRow
android:id="#+id/mainRow"
android:layout_weight="1" >
<FrameLayout
android:id="#+id/mainLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
</TableRow>
<TableRow
android:id="#+id/footerRow"
android:layout_weight="1" >
<FrameLayout
android:id="#+id/footerLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
</TableRow>
</TableLayout>
</RelativeLayout>
... and populate it with graphs from the GraphView api as follows:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int[] assetLayouts = new int[]{
R.id.mainLayout,
R.id.footerLayout,
};
int count = 0;
for (int assetLayout :assetLayouts ) {
GraphViewSeries exampleSeries = new GraphViewSeries(new GraphViewData[] {
new GraphViewData(1, 2.0d)
, new GraphViewData(2, 1.5d)
, new GraphViewData(3, 2.5d)
, new GraphViewData(4, 1.0d)
});
GraphView graphView = new LineGraphView(this, "" + ++count);
graphView.addSeries(exampleSeries);
FrameLayout layout = (FrameLayout) findViewById(assetLayout);
layout.addView(graphView);
}
}
This behaves as expected and divides the screen in two parts, with one graph taking up the first top half, and the second one taking up the other bottom half. Now, I want to give the top graph/row more weight, i.e., it should be proportionally larger than the lower graph/row. So, what i did was set the top row (mainRow) to android:layout_weight="2". I expect that the top row now should be twice as large as the bottom row, however, the opposite happens. The top one only get 33%, while the bottom one gets 66% of the screen real estate. This is opposite of the documentation I found:
For example, if there are three text fields and two of them declare a
weight of 1, while the other is given no weight, the third text field
without weight will not grow and will only occupy the area required by
its content. The other two will expand equally to fill the space
remaining after all three fields are measured. If the third field is
then given a weight of 2 (instead of 0), then it is now declared more
important than both the others, so it gets half the total remaining
space, while the first two share the rest equally.
http://developer.android.com/guide/topics/ui/layout/linear.html
What is going on here?
Sure I can work around it and just give the opposite weights of what I thought was right, but I'd rather do it right.
Also when setting weight, set width (if orientation is horizontal) or height (if vertical) to 0dp. That way the width/height is calculated properly.
I want to get EditText selection start when user click in EditText (touch).
I do with this code :
int startIndex = txtMean.getSelectionStart();
this always return 0;
and EditText xml code:
<EditText
android:id="#+id/txtMean"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#null"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint=""
android:inputType="textMultiLine"
android:scrollbars="none" />
my code work in android 2.* but don't work in 4.*
txtMean.getSelectionStart();
getSelectionStart doesn't relate to the user's last touch or click per se. It relates to the text selection on the screen. By default, when the user does a long click text-handles will come up, and allow the user to expand a highlighted text selection. It's the highlighted text that refers to the selection, which is not necessarily where the user last touched.
When the user makes a text selection it becomes highlighted because the EditText will apply a SelectionSpan to the character sequence, and getSelectionStart will return the start value of this span.
Update, Solution Help:
#Override
public boolean onTouchEvent(MotionEvent event) {
// this = EditText;
// will give you the position of the nearest chracter.
int offset = this.getOffsetForPosition(event.getX(), event.getY());
I have some code which uses a textview with a linkmovementmethod so that individual words can be clicked.
However, what I really need is a scrollingmovementmethod. When I do that, the individual spanned segments are no longer clickable.
Unfortunately this means it uses a vertical scroll, even though horizontal scroll ( android:scrollHorizontally="true") is specified.
I'm trying to figure out how to retain the ability to catch onClick on the individual span words, and still allow it to be vertically scrollable.
Can someone provide advice on how to get the best of both worlds?
String page = getPage();
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setText(page, BufferType.SPANNABLE);
Spannable ssPage = (Spannable) textView.getText();
Integer[] indices = getSpaceIndices(textView.getText().toString(), ' ');
int start = 0;
int end = 0;
// to cater last/only word loop will run equal to the length of indices.length
for (int i = 0; i <= indices.length; i++) {
ClickableSpan clickSpan = new ClickableSpan() {
#Override
public void onClick(View widget) {
TextView tv = (TextView) widget;
String s = tv
.getText()
.subSequence(tv.getSelectionStart(),
tv.getSelectionEnd()).toString();
Log.d("called", s);
Speak (s);
//textView.scrollBy(tv.getWidth(), tv.getHeight());
}
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
};
// to cater last/only word
end = (i < indices.length ? indices[i] : ssPage.length());
ssPage.setSpan(clickSpan, start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
start = end + 1;
}
Layout:
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:alpha="245"
android:ellipsize="end"
android:gravity="top"
android:paddingBottom="15dip"
android:scrollHorizontally="true"
android:scrollbars="horizontal"
android:text="#string/hello_world"
android:textColorLink="#android:color/black"
android:textSize="20dip"
tools:context=".ReadActivity" />
LinkMovementMethod already "traverses links in the text buffer and scrolls if necessary." so you can use it if you want both clickable links and scrollable views.
Then you have to adjust the textview's layout like this:
android:maxLines="3"
android:scrollbars="vertical"
By setting maxLines, the excess lines after it, will exceed the height, thus resulting in a scrollable textview.
I have a TextView within a ScrollView, which currently scrolls to the bottom of the TextView.
The TextView is filled dynamically constantly updating (the TextView is essentially acting as an actions console).
However, the problem I am having is that when the dynamic text is added to the ScrollView, the user can scroll past the text into black space, which is increasing everytime more content is added to the TextView.
I have tried various different apporaches however none of these gave the right outcome. I cannot use maxLines or define height of the layouts as I need this to be dynamic for the various screen sizes, which the number of lines visible constantly changing.
I had also orginally done this progromatically, however this was crashing at random time and therefore would like to keep it in my layout (better usabilty), example code below:
final int scrollAmount = update.getLayout().getLineTop(update.getLineCount()) - update.getHeight();
if(scrollAmount > 0)
{
update.scrollTo(0, scrollAmount);
}
The code below is my current layout xml being used to automatically scroll my TextView to the bottom as content is added:
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#+id/spacer2"
android:layout_below="#+id/spacer1"
android:fillViewport="true" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/battle_details"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="12dp"
android:layout_gravity="bottom" />
</LinearLayout>
</ScrollView>
EDIT - This is the code I am using to add text to my TextView:
private void CreateConsoleString()
{
TextView update = (TextView)findViewById(R.id.battle_details);
String ConsoleString = "";
// BattleConsole is an ArrayList<String>
for(int i = 0; i < BattleConsole.size(); i++)
{
ConsoleString += BattleConsole.get(i) + "\n";
}
update.setText(ConsoleString);
}
EDIT 2 - I add content to the BattleConsole like this:
BattleConsole.add("Some console text was added");
CreateConsoleString();
To sum up my only issue is the ScrollView and/or TextView is adding blank space to the bottom rather than stop the user from scrolling at the last line of text. Any help or guidence as to where I am going wrong would be much appreciated.
It looks like that when you call
BattleConsole.get(i)
it sometimes returns an empty String so you are just basically adding new lines to your TextView.
You can do this for example:
StringBuilder consoleString = new StringBuilder();
// I'm using a StringBuilder here to avoid creating a lot of `String` objects
for(String element : BattleConsole) {
// I'm assuming element is not null
if(!"".equals(element)) {
consoleString.append(element);
consoleString.append(System.getProperty("line.separator")); // I'm using a constant here.
}
}
update.setText(consoleString.toString());
If you could post the code of BattleConsole I could help you more.
As a footnote: it is encouraged to use camelCase in java. Only class names start with capital letters in java according to the convention.