I'm trying to program a syntax highlighter for Android. The highlighting algorithm which runs in a separate AsyncTask thread itself works great, and returns a SpannableString with all the necessary formatting.
However, whenever I do editText.setText(mySpannableString, BufferType.SPANNABLE) to display the highlighted text the EditText scrolls back to the beginning and selects the beginning of the text.
Obviously, this means that the user cannot keep typing whilst the text is being processed by the syntax highlighter. How can I stop this? Is there any way I can update the text without the EditText scrolling? Here is an outline of the code:
public class SyntaxHighlighter extends Activity {
private EditText textSource;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.editor);
textSource = (EditText) findViewById(R.id.codeSource);
// Syntax Highlighter loaded text
new XMLHighlighter().execute(textSource.getText().toString());
}
// Runs on Asyncronous Task
private class XMLHighlighter extends AsyncTask<String, Void, SpannableString> {
protected SpannableString doInBackground(String... params) {
return XMLProcessor.HighlightXML(params[0]);
}
protected void onPostExecute(SpannableString HighlightedString) {
textSource.setText(HighlightedString, BufferType.SPANNABLE);
}
}
}
Alternatively, there is a method called setTextKeepState(CharSequence text). See TextView docs.
I suggest the following:
protected void onPostExecute(SpannableString HighlightedString) {
int i = textSource.getSelectionStart();
textSource.setText(HighlightedString, BufferType.SPANNABLE);
textSource.setSelection(i);
}
to place the cursor back to its position after you changed the content.
Related
Inside of my enteredName method, I want to be able to check to make sure the user has entered their name inside of the name text box. I know that I already made the input equal to name by using findViewById. What I'm having trouble with is how do you get that input so you can use in the enteredName method as well? Do I need to use findViewById each time I'm referring to what the user entered into the box?
public class MainActivity extends AppCompatActivity {
EditText name;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
name = findViewById(R.id.name);
}
public boolean enteredName(){
}
Your first question.
how do you get that input so you can use in the enteredName method as
well?
We can get their input by using this one.
name.getText().toString();
So you want to use in method boolean enteredName(). You can do something like this.
public boolean enteredName(){
if (name.getText().toString().isEmpty()) return false;
return true;
}
your 2nd qestion.
Do I need to use findViewById each time I'm referring to what the user
entered into the box?
Yes. it really necessary to link using findViewById. If you don't want to use it, you can use butterKnife lib.
I'm facing this problem with TextView. It is not erasing the previous instances of data.
When I'm running my application in emulator it displays output data in TextView. That's fine. But when I'm clicking back button in my emulator and re opening the application it does not clear the previous data. Instead it appends the data to already existing data.
Any help is appreciated.
My code is as below:
public class MainActivity extends ActionBarActivity
{
private TextView mTextView;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text_test);
new Thread(new TestLocalHost()).start();
}
private class TestLocalHost implements Runnable
{
#Override
public void run()
{
final String s = JSONParser.doGet("http://192.168.0.107:15071/GetResult.ashx?op=getInfo",null);
runOnUiThread(new Runnable()
{
#Override
public void run()
{
mTextView.setText(s);
}
});
}
}
}
It's happening because your app still lives in the emulator memory, you need to force kill it if you want your app to run again from scratch.
You should click on the "running processes" button and swipe left/right the app process.
In the scenario that you described the activity wasn't destroyed yet, and when you reopen it only its onResume() method is being called.
My presentation is working finally. I have one main activity for my first screen and one Presentation for my second Screen.
My problem is, that I can't change the content on my presentation view.
Why can't I change a TextView after the presentation is shown on the second screen?
Calling the method changeText("Test123") in the MainActivity crashes my app.
public class MainActivity extends Activity {
private PresentationActivity presentationActivity;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// init Presentation Class
DisplayManager displayManager = (DisplayManager) this.getSystemService(Context.DISPLAY_SERVICE);
Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
if (presentationDisplays.length > 0) {
// If there is more than one suitable presentation display, then we could consider
// giving the user a choice. For this example, we simply choose the first display
// which is the one the system recommends as the preferred presentation display.
Display display = presentationDisplays[0];
PresentationActivity presentation = new PresentationActivity(this, display);
presentation.show();
this.presentationActivity = presentation;
}
}
public void changeText (String s) {
this.presentationActivity.setText(s);
}
}
public class PresentationActivity extends Presentation {
private TextView text;
private PresentationActivity presentation;
public PresentationActivity(Context outerContext, Display display) {
super(outerContext, display);
// TODO Auto-generated constructor stub
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_presentation);
TextView text = (TextView) findViewById(R.id.textView1);
this.text = text;
// works fine:
text.setText("test");
}
public void setText(String s){
// error
this.text.setText(s);
}
Well, I looked in the LogCat.
The exception was:
E/AndroidRuntime(13950): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
The code in my MainActivity runs on another thread. To do UI work from here I need to use runOnUiThread. This solution I found in this answer.
My changeText methode looks like this now:
public void changeText (String s) {
runOnUiThread(new Runnable() {
public void run() {
presentationActivity.setImageView(position);
}
});
}
Thanks for the help! Now I know how to use LogCat for things like that.
You got this issue because the context of presentation is different from that of the containing Activity:
A Presentation is associated with the target Display at creation time
and configures its context and resource configuration according to the
display's metrics.
Notably, the Context of a presentation is different from the context
of its containing Activity. It is important to inflate the layout of a
presentation and load other resources using the presentation's own
context to ensure that assets of the correct size and density for the
target display are loaded.
Hope this will justify your mentioned solution as well.
Is there any way to call a listener manually from code?
More background information: I use a Spinner and a DatePicker. With the Spinner you can choose a reason for staying at home (maybe your ill, maybe you have vacation) and with the DatePicker you can choose the date how long you will not be available. With these two pieces of information I build up a string for a TextView and show the same data in a compact way. The building process for the string is set by some listeners which recognize changes on one of the two controls and build and set up the new string.
If I start the program and read some data from a server the string will not be build (clearly because nothing changed and no listener will called).
The workaround is to build the string in my own onLoaddata() method. But I think it would be smoother way to call one listener to build the string for me. I also can "call" a listener if I just do some fake .updateDate but I don't think it’s a good idea to create useless calls...
Maybe someone of you have a good hint for me?
Use the following pattern:
public class YourActivity extends Activity implements OnClickListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Some initialization here
findViewById(R.id.some_button).setOnClickListener(this);
...
// Here you want to update your view
updateTextView();
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.some_button:
// Here you also want to update your view
updateTextView();
break;
...
}
}
private void updateTextView() {
// Here you update your view
...
}
}
So im trying to use a indeterminate progressbar in my custom title header to show any background work. Right now um using an asynctask to show and hide the progressbar using the pre and post methods:
Here's the class that contains everything:
public abstract class QuadrosMobileActivity extends Activity{
protected static volatile ProgressBar progressHeader = null;
protected static int progressBarstate=ProgressBar.INVISIBLE;
//this method will launch respejcting oncreate logic for each activity
public abstract void initActivity();
public int getProgressBarstate(){
return progressBarstate;
}
public void setProgressBarstate(int state){
progressBarstate=state;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
//set theme first for showing correct header
setTheme(getThemeId());
super.onCreate(savedInstanceState);
//in case the screen will have a header
if(getThemeId()!=R.style.CustomThemeNoHeader){
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(getLayoutId());
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_window_title);
//set text header
((TextView)findViewById(R.id.header_title)).setText(getWindowTitle());
progressHeader= (ProgressBar) findViewById(R.id.progresspinner);
progressHeader.setVisibility(getProgressBarstate());
}else {
setContentView(getLayoutId());
}
//execute activity logic
initActivity();
}
private class ProgressBarThread extends AsyncTask<Void, Void, Void>{
#Override
protected void onPreExecute() {
progressHeader.setVisibility(ProgressBar.VISIBLE);
setProgressBarstate(ProgressBar.VISIBLE);
}
#Override
protected Void doInBackground(Void... params) {
updateResultsInUi();
return null;
}
#Override
protected void onPostExecute(Void result) {
progressHeader.setVisibility(View.INVISIBLE);
setProgressBarstate(View.INVISIBLE);
}
}
}
This is my main activity class where all my screen subactivities extend from.
The problem i have is, if i start the asynctask, the progressbar gets visible(all in Activity A) and while this happens if i go to another activity(B), the progressbar still appears correctly and gets hidden when the background work ends, all correct behaviour. BUT if i press the back button the previous activity(A) shows with the progressbar visible.
Here's a simple diagram:
start Activity A:
launches asynctask
shows progressbar
does background work
go to Activity B:
progressbar still shows
ends background work
hides progressbar *correct behaviour
go back to Activity A:
progressbar is visible *incorrect behaviour
...
I tried this on the onResume method:
#Override
protected void onResume() {
super.onResume();
if(getThemeId()!=R.style.CustomThemeNoHeader){
System.out.println("visible: " + ProgressBar.VISIBLE+" : " + getProgressBarstate());
progressHeader.setVisibility(getProgressBarstate());
};
}
But to no avail, can i really use the onResume to alter the progressbar state when pressing the back button???
This the method i tried:
#Override
public void onRestart ()
{
super.onRestart();
if (getThemeId() != R.style.CustomThemeNoHeader)
{
Toast.makeText(this, "visible(0): " + ProgressBar.VISIBLE+" : Current :" + getProgressBarstate(),Toast.LENGTH_LONG).show();
// doesnt work
QuadrosMobileActivity.progressHeader.setVisibility(getProgressBarstate());
// works
((TextView)findViewById(R.id.header_title)).setText("Tretas");
}
}