I know this question was asked and somewhat answered before, but none of the solutions I found so far really worked out for me: I have a custom seekBar with a large square block for the thumb, which I draw on the fly, and I want the seekBar progress to change only when the thumb is being dragged.
Large thumb seekBar
I looked around and tried out all of the proposed solutions I could find, and some worked better then others, but they all have a big issue (for me at least): if I detect the boundaries of the thumb in order to start dragging the thumb, the onTouch ACTION_DOWN event makes the thumb jump to whatever position the pointer is on the screen, within the thumb's boundaries, of course.
If the thumb size is small, this is almost a non issue, but on a large thumb, this behavior is really annoying.
To make matters worse, if I declare a thumb offset of 0 to keep the thumb inside the seekBar, the jumping behavior changes with respect to where on the seekbar the thumb is: indetermined jump direction right in the middle of the seekbar, or it jumps to the right if the thumb is on the left half of the seekbar, or to the left if the thumb is on the right half of the seekbar.
Here's my starting code, I removed all of my failed attempts to make the seekBar's thumb move only when a drag starts:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/progressText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/mySeekbar"
app:layout_constraintVertical_bias="0.154" />
<com.example.thumbonlyseekbar.MySeekBar
android:id="#+id/mySeekbar"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="150dp"
android:layout_marginEnd="20dp"
android:indeterminate="false"
android:max="99"
android:progress="0"
android:progressDrawable="#drawable/my_seekbar"
android:thumb="#drawable/thumb"
android:thumbOffset="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MySeekbar.java
package com.example.thumbonlyseekbar;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
public class MySeekBar extends androidx.appcompat.widget.AppCompatSeekBar {
Drawable mThumb;
public MySeekBar(#NonNull Context context) {
super(context);
}
public MySeekBar(#NonNull Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySeekBar(#NonNull Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setThumb(Drawable thumb) {
super.setThumb(thumb);
mThumb = thumb;
}
public Drawable getSeekBarThumb() {
return mThumb;
}
}
MainActivity.java
package com.example.thumbonlyseekbar;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
MySeekBar customSeekBar;
TextView progressValueText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
customSeekBar = findViewById(R.id.mySeekbar);
progressValueText = findViewById(R.id.progressText);
progressValueText.setText(String.valueOf(customSeekBar.getProgress()));
customSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
progressValueText.setText(String.valueOf(progress));
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}
thumb.xml
package com.example.thumbonlyseekbar;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
MySeekBar customSeekBar;
TextView progressValueText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
customSeekBar = findViewById(R.id.mySeekbar);
progressValueText = findViewById(R.id.progressText);
progressValueText.setText(String.valueOf(customSeekBar.getProgress()));
customSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
progressValueText.setText(String.valueOf(progress));
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}
my_seekbar.xml
package com.example.thumbonlyseekbar;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
MySeekBar customSeekBar;
TextView progressValueText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
customSeekBar = findViewById(R.id.mySeekbar);
progressValueText = findViewById(R.id.progressText);
progressValueText.setText(String.valueOf(customSeekBar.getProgress()));
customSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
progressValueText.setText(String.valueOf(progress));
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}
If you would like to see an example of what I mean by a no-thumb-jump seekBar, please take a look at the AMPLIFi Remote app in Google Play Store (it may only work on older devices, it works on my OREO 8.1, you may need to try it in an emulator that allows downloads from Google Play if you don't have an older device). The thumb's progress is unbelievably smooth, and there's absolutely no jumping around. Whoever wrote that app did an awesome job!
Many, many thanks, any suggestion is much appreciated!
Related
here is a very simple app.
I would like te to scroll the view automatically while it's being spoken. As of right now, I am highlighting the text while it's spoken. However, when the text is long, I would like the text container to scroll down the text automatically while it's been spoken.
Imagine like subtitles showing in a movie. However, instead of subtitles, I have text. And instead of an actor speaking something, I have TTS.
Any help is appreciated.
Here is my code:
The XML design:
<TextView
android:id="#+id/text_view_id"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top"
android:textSize="60dp"
android:textStyle="bold"
android:text="It's possible to perform frequency separation just like in Photoshop, and there are three types of healing tools. Photopea supports actions as well, so it's possible to create frequency separation layers with a single click."
android:hint=""
/>
<Button
android:id="#+id/botao"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="Rodar" />
And here is my java code:
package com.example.myapplication;
import android.graphics.Color;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.ScrollingMovementMethod;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.R;
public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener, View.OnClickListener {
TextToSpeech tts;
String sentence = "It's possible to perform frequency separation just like in Photoshop, and there are three types of healing tools. Photopea supports actions as well, so it's possible to create frequency separation layers with a single click.";
TextView textView;
Button botao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text_view_id);
textView.setText(sentence);
tts = new TextToSpeech(this, this);
botao = (Button) findViewById(R.id.botao) ;
botao.setOnClickListener(MainActivity.this);
}
// TextToSpeech.OnInitListener (for our purposes, the "main method" of this activity)
public void onInit(int status) {
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onStart(String utteranceId) {
Log.i("XXX", "utterance started");
}
#Override
public void onDone(String utteranceId) {
Log.i("XXX", "utterance done");
}
#Override
public void onError(String utteranceId) {
Log.i("XXX", "utterance error");
}
#Override
public void onRangeStart(String utteranceId,
final int start,
final int end,
int frame) {
Log.i("XXX", "onRangeStart() ... utteranceId: " + utteranceId + ", start: " + start
+ ", end: " + end + ", frame: " + frame);
// onRangeStart (and all UtteranceProgressListener callbacks) do not run on main thread
// ... so we explicitly manipulate views on the main thread:
runOnUiThread(new Runnable() {
#Override
public void run() {
Spannable textWithHighlights = new SpannableString(sentence);
textWithHighlights.setSpan(new BackgroundColorSpan(Color.YELLOW), start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(textWithHighlights);
textView.setMovementMethod(new ScrollingMovementMethod());
}
});
}
});
}
public void startClicked(View ignored) {
tts.speak(sentence, TextToSpeech.QUEUE_FLUSH, null, "doesn't matter yet");
}
#Override
public void onClick(View view) {
tts.speak(sentence, TextToSpeech.QUEUE_FLUSH, null, "doesn't matter yet");
}
}
Any advice?
I am current working through a SeekBar example located here. This example has one seek bar, but I wanted to see what would happen if I added two more seek bars.
While the listeners all seem to still work, I want to add the name of the SeekBar to the toast that shows up while tracking the the touch bar. However, I can't just add the name of the variable to the toast ( the passed in seekBar is a widget ) and this doesn't fit the function call of toString().
How would I add the name of the seekBar that I'm focusing on to the Toast?
MainActivity.java:
package com.javatpoint.seekbar;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;
public class MainActivity extends Activity implements OnSeekBarChangeListener{
SeekBar seekBar1, seekBar2, seekBar3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seekBar1=(SeekBar)findViewById(R.id.seekBar1);
seekBar1.setOnSeekBarChangeListener(this);
seekBar2=(SeekBar)findViewById(R.id.seekBar2);
seekBar2.setOnSeekBarChangeListener(this);
seekBar3=(SeekBar)findViewById(R.id.seekBar3);
seekBar3.setOnSeekBarChangeListener(this);
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
//This is where I want to print out the variable name
Toast.makeText(getApplicationContext(), "seekbar progress: "+progress, Toast.LENGTH_SHORT).show();
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
Toast.makeText(getApplicationContext(),seekBar +"seekbar touch started!", Toast.LENGTH_SHORT).show();
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
Toast.makeText(getApplicationContext(),"seekbar touch stopped!", Toast.LENGTH_SHORT).show();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
activity_main.xml
<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=".MainActivity" >
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginTop="120dp" />
<SeekBar
android:id="#+id/seekBar2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_below="#id/seekBar1"/>
<SeekBar
android:id="#+id/seekBar3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_below="#id/seekBar2"/>
Someone posted a link to this post last night detailing the use of tags to solve my problem, but unfortunately they either removed their post or it was deleted before I could mark it as the correct answer.
I was able to use the info in that post to set a string as the tag for each seekBar, and then pass that into a new function that took the tag as well as the progress from the seekbar to show different toasts for each seekbar.
Here was the final version of the MainActivity
package com.javatpoint.seekbar;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;
public class MainActivity extends Activity implements OnSeekBarChangeListener{
SeekBar seekBar1, seekBar2, seekBar3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seekBar1=(SeekBar)findViewById(R.id.seekBar1);
seekBar1.setOnSeekBarChangeListener(this);
seekBar1.setTag("1");
seekBar2=(SeekBar)findViewById(R.id.seekBar2);
seekBar2.setOnSeekBarChangeListener(this);
seekBar2.setTag("2");
seekBar3=(SeekBar)findViewById(R.id.seekBar3);
seekBar3.setOnSeekBarChangeListener(this);
seekBar3.setTag("3");
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
changeValue(seekBar.getTag(), progress);
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
Toast.makeText(getApplicationContext(),"seekbar touch started!", Toast.LENGTH_SHORT).show();
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
Toast.makeText(getApplicationContext(),"seekbar touch stopped!", Toast.LENGTH_SHORT).show();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void changeValue(Object a, int progress){
if(a=="1"){
Toast.makeText(getApplicationContext(), "seekbar1 progress: "+progress, Toast.LENGTH_SHORT).show();
}
if(a=="2"){
Toast.makeText(getApplicationContext(), "seekbar2 progress: "+progress, Toast.LENGTH_SHORT).show();
}
if(a=="3"){
Toast.makeText(getApplicationContext(), "seekbar3 progress: "+progress, Toast.LENGTH_SHORT).show();
}
}
}
Pretty new to android world, I am having an issue playing audio when clicking a button. The interesting/weird aspect of it is that same code works on my mainactivity but not on secondactivity that I have set up. I am using the same exact code that works on mainactivity. I used that code on mainactivity just to test it, keep in mind no media player has been declared or defined in mainactivity. I did that just to test to see if code works.
Here is my xml:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="15sp"
android:layout_marginBottom="15sp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="press button to play audio"
android:textSize="40sp"
android:textColor="#ffff"
android:fontFamily="cursive"
android:textStyle="bold"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="10sp"
android:layout_gravity="center"
>
<Button
android:id="#+id/AudioButton"
android:layout_width="wrap_content"
android:layout_height="50sp"
android:text="play"
android:textSize="22sp"
android:textColor="#ffff"
android:layout_marginRight="10dp"
/>
</LinearLayout>
Here is JAVA:
package nameiscleared;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class SecondActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button start = (Button) findViewById(R.id.AudioButton);
start.setOnClickListener(new View.OnClickListener() {
MediaPlayer mp = MediaPlayer.create(getApplicationContext(), R.raw.audioname);
#Override
public void onClick(View view) {
mp.start();
}
});
}
}
It just my assumption, I think you are not releasing the MediaPlayer when you use it in MainActivity. That is why it is not working on secondActivity. Another mistake is, MediaPlayer mp = MediaPlayer.create(getApplicationContext(), R.raw.audioname); need to be in onClick, not in View.OnClickListener() bracket. You need to keep in mind that after you used MediaPlayer, you need to release it when it is no longer being used.
A MediaPlayer can consume valuable system resources. Therefore, you should always take extra precautions to make sure you are not hanging on to a MediaPlayer instance longer than necessary. When you are done with it, you should always call release() to make sure any system resources allocated to it are properly released. For example, if you are using a MediaPlayer and your activity receives a call to onStop(), you must release the MediaPlayer, because it makes little sense to hold on to it while your activity is not interacting with the user (unless you are playing media in the background, which is discussed in the next section). When your activity is resumed or restarted, of course, you need to create a new MediaPlayer and prepare it again before resuming playback - Android Developers documentation.
The correct implementation supposed to be like this;
MainActivity
public class MainActivity extends AppCompatActivity{
private Button playBtn, startActivityBtn;
private MediaPlayer mediaPlayer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
playBtn = (Button)findViewById(R.id.playBtn);
playBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mediaPlayer = MediaPlayer.create(MainActivity.this, Settings.System.DEFAULT_RINGTONE_URI);
mediaPlayer.start();
}
});
startActivityBtn = (Button)findViewById(R.id.startActivity);
startActivityBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
#Override
protected void onStop() {
super.onStop();
if(null != mediaPlayer){
if(mediaPlayer.isPlaying())
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
}
SecondActivity
public class SecondActivity extends AppCompatActivity {
private Button playBtn;
private MediaPlayer mediaPlayer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_econd);
playBtn = (Button)findViewById(R.id.playBtn);
playBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mediaPlayer = MediaPlayer.create(SecondActivity.this, Settings.System.DEFAULT_RINGTONE_URI);
mediaPlayer.start();
}
});
}
#Override
protected void onStop() {
super.onStop();
if(null != mediaPlayer){
if(mediaPlayer.isPlaying())
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
}
I am not including the layout because both of the layout are very simple. MainActivity has two button, to play and to start another activity. SecondActivity has only play button.
I need your help on this. I am going to make an app using ViewPager and I since I never been programming for android before I thought it would be good to make a sample app first. I want to use the ViewPager a little bit differently than the classic list-of-items-style so I made an app that will show all the colors (or every 10th color) from #000000 to #FFFFFF.
It doesn't work. I've started the app on the emulator but I just get a white screen. If the default position of the ViewPager when starting is 0 then the color should be black. And when I try to make a breakpoint the program doesn't stop, or it's never reaching the point. I'm using eclipse.
These are the files of the project
MainActivity.java
package com.example.colorswipe;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
public class MainActivity extends Activity {
private ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPager=(ViewPager)this.findViewById(R.id.pager);
mPager.setAdapter(new MyAdapter());
}
private class MyAdapter extends PagerAdapter {
#Override
public int getCount() {
return 0xFFFFFF/10;
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
return false;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
ColorView view=new ColorView(container.getContext());
view.setBackgroundColor(android.graphics.Color.parseColor(String.format("#%06X", position*10)));
view.setText(position);
container.addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
ColorView.java
package com.example.colorswipe;
import android.content.Context;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ColorView extends LinearLayout {
private TextView tv;
public ColorView(Context context) {
super(context);
LayoutParams params=new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
this.setLayoutParams(params);
TextView tv=new TextView(context);
this.tv=tv;
this.addView(tv);
}
public void setText(int position) {
tv.setText(Integer.toString(position).toCharArray(), 0, Integer.toString(position).length());
}
}
EDIT - the view pager is there , but I think you don't resolve the color correctly.
I found the problem. isViewFromObject must be implemented as return arg0==arg1.
I have a basic android app set up with a scroll view and text added to it dynamically.
I want it to scroll to the bottom when text is added (which already happens) but I only want it to scroll to the bottom if you are already at the bottom, so if you're reading something it doesn't just scroll.
Here's what I have so far.
private void AddText(final String msg){
this.runOnUiThread(new Runnable() {
public void run() {
TextView log = (TextView) findViewById(R.id.chatlog);
if(log.getText().equals("Loading...")){
log.setText(msg);
}else{
ScrollView scroller = (ScrollView) findViewById(R.id.scroll_container);
//set current scroll position
int scroll_pos = scroller.getScrollY();
//scroll to bottom
scroller.fullScroll(ScrollView.FOCUS_DOWN);
//set bottom position
int scroll_bot = scroller.getScrollY();
//add the text
log.append("\r\n" + msg);
//if you weren't at the bottom
//scroll back to where you were.
//This isn't working, scroll bot is the same
//as scroll pos.
if(scroll_pos != scroll_bot){
scroller.scrollTo(0, scroll_pos);
}
//System.out.println("Pos: " + scroll_pos);
//System.out.println("Bot: " + scroll_bot);
}
}
});
}
The best solution I found so far is to use scrollView.post() method with a runnable that will be invoked after text change:
final ScrollView scrollView = (ScrollView) findViewById(R.id.consoleTab);
TextView textView = (TextView) findViewById(R.id.consoleView);
boolean autoScroll = (textView.getBottom() - (scrollView.getHeight() + scrollView.getScrollY())) <= 0;
textView.setText(state.getConsole().getText());
if (autoScroll) {
scrollView.post(new Runnable() {
public void run() {
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
Here is a working example I got running in the emulator and on my galaxy s.
Basically there are two buttons to add text to the bottom text view, depending on your devices size only the second of these should be usable to see the autoscrolling. The textview being edited uses the ontextchangedlistener to check the scroll position before its text is changed and then to call a delayed (so the screen can update with the additional text) autoscroll where appropriate, after the text has changed.
The layout xml is as follows:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scrollmain">
<RelativeLayout
android:id="#+id/mainRelative"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome to the scrolling test application!" />
<Button
android:id="#+id/firstTextAddButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/titleText"
android:text="Click me to add text to the textview below without scrolling"/>
<Button
android:id="#+id/secondTextAddButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/firstTextAddButton"
android:layout_marginTop="380dp"
android:text="Click me to add text to the textview below and scroll (if you are currently scrolled all the way to the bottom)"/>
<TextView
android:id="#+id/textToEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/secondTextAddButton"
android:textSize="18sp"
android:text="Some text to get us started."/>
/>
</RelativeLayout>
</ScrollView>
And the code for the activity is as follows:
package code.example.scrollingontextchange;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
public class MainActivity extends Activity {
private int scroll_pos;
private int maxScrollPosition;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView)findViewById(R.id.textToEdit);
tv.addTextChangedListener(new TextWatcher(){
#Override
public void afterTextChanged(Editable s) {
if(scroll_pos == maxScrollPosition)
{
Handler h = new android.os.Handler()
{
#Override
public void handleMessage(android.os.Message msg)
{
switch(msg.what)
{
case 0 :
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ScrollView scrll = (ScrollView)findViewById(R.id.scrollmain);
scrll.fullScroll(ScrollView.FOCUS_DOWN);
break;
default :
break;
}
}
};
h.sendEmptyMessage(0);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
ScrollView scrll = (ScrollView)findViewById(R.id.scrollmain);
RelativeLayout rl = (RelativeLayout)findViewById(R.id.mainRelative);
scroll_pos = scrll.getScrollY();
maxScrollPosition = rl.getHeight() - scrll.getHeight();
}
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// TODO Auto-generated method stub
}
});
OnClickListener addTextOnClick = new OnClickListener() {
#Override
public void onClick(View v) {
TextView tv = (TextView)findViewById(R.id.textToEdit);
tv.setText(tv.getText() + "\nA long time ago, in a galaxy s far far away............");
}
};
Button b = (Button)findViewById(R.id.firstTextAddButton);
b.setOnClickListener(addTextOnClick);
Button b2 = (Button)findViewById(R.id.secondTextAddButton);
b2.setOnClickListener(addTextOnClick);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Hope this helps.
Don't ask me why, but setting your TextView's gravity to bottom it does exactly what you want.