I'm a beginner with Android, so please be kind if this is a stupid question.
I'm trying to dynamically update four TextViews. Whenever I try to update them, the program crashes.
I don't understand the explanation "Only the original thread that created a view hierarchy can touch its views."
Here is my class:
Globals g = new Globals();
String l1 = "";
String l2 = "";
String l3 = "";
String l4 = "";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
}
#Override
public void onStart() {
super.onStart();
//View view = getLayoutInflater().inflate(R.layout.activity_game, null);
run ();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_game, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private void nextLine (String s, int pauseTime, TextView first, TextView second, TextView third, TextView fourth)
{
l4 = l3;
l3 = l2;
l2 = l1;
l1 = s;
first.setText (l1);
second.setText (l2);
third.setText (l3);
fourth.setText(l4);
try
{
Thread.sleep (pauseTime);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
#Override
public void onPause ()
{
super.onPause ();
}
#Override
public void run () {
Thread thread = new Thread() {
#Override
public void run() {
TextView first = (TextView) findViewById(R.id.botLine);
TextView second = (TextView) findViewById(R.id.secondLine);
TextView third = (TextView) findViewById(R.id.thirdLine);
TextView fourth = (TextView) findViewById(R.id.fourthLine);
first.setTypeface(Typeface.MONOSPACE);
second.setTypeface(Typeface.MONOSPACE);
third.setTypeface(Typeface.MONOSPACE);
fourth.setTypeface(Typeface.MONOSPACE);
first.setTextSize((float) g.getTextSizeInt());
second.setTextSize((float) g.getTextSizeInt());
third.setTextSize((float) g.getTextSizeInt());
fourth.setTextSize((float) g.getTextSizeInt());
nextLine("1", 1000, first, second, third, fourth);
nextLine("2", 1000, first, second, third, fourth);
}
};
thread.start();
}
...and this is my LogCat:
08-05 02:33:34.129 14823-14854/com.mycompany.TestApp E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-190
Process: com.mycompany.TestApp, PID: 14823
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:909)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:4690)
at android.view.View.invalidateInternal(View.java:11801)
at android.view.View.invalidate(View.java:11765)
at android.view.View.invalidate(View.java:11749)
at android.widget.TextView.checkForRelayout(TextView.java:6858)
at android.widget.TextView.setText(TextView.java:4057)
at android.widget.TextView.setText(TextView.java:3915)
at android.widget.TextView.setText(TextView.java:3890)
at com.mycompany.TestApp.Game.nextLine(Game.java:64)
at com.mycompany.TestApp.Game.access$000(Game.java:13)
at com.mycompany.TestApp.Game$1.run(Game.java:106)
08-05 02:33:34.266 14823-14839/com.mycompany.TestApp W/EGL_emulation﹕ eglSurfaceAttrib not implemented
08-05 02:33:34.266 14823-14839/com.mycompany.TestApp W/OpenGLRenderer﹕ Failed to set EGL_SWAP_BEHAVIOR on surface 0xb3f1ff20, error=EGL_SUCCESS
...and the XML code:
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="#+id/botLine"
android:layout_marginBottom="70dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="#+id/secondLine"
android:layout_marginBottom="70dp"
android:layout_above="#+id/botLine"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="#+id/thirdLine"
android:layout_marginBottom="70dp"
android:layout_above="#+id/secondLine"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="#+id/fourthLine"
android:layout_marginBottom="70dp"
android:layout_above="#+id/thirdLine"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
</RelativeLayout>
Program is getting crashed because you are trying to update the text on TextView from a non UI Thread.
replace your run() method with this:
public void run () {
Handler handler = new Handler(Looper.getMainLooper);
handler.post(new Runnable(){
#Override
public void run(){
TextView first = (TextView) findViewById(R.id.botLine);
TextView second = (TextView) findViewById(R.id.secondLine);
TextView third = (TextView) findViewById(R.id.thirdLine);
TextView fourth = (TextView) findViewById(R.id.fourthLine);
first.setTypeface(Typeface.MONOSPACE);
second.setTypeface(Typeface.MONOSPACE);
third.setTypeface(Typeface.MONOSPACE);
fourth.setTypeface(Typeface.MONOSPACE);
first.setTextSize((float) g.getTextSizeInt());
second.setTextSize((float) g.getTextSizeInt());
third.setTextSize((float) g.getTextSizeInt());
fourth.setTextSize((float) g.getTextSizeInt());
nextLine("1", 1000, first, second, third, fourth);
nextLine("2", 1000, first, second, third, fourth);
}
});
}
You can't change the UI from another thread, you should use a method that deals with the main thread, for example you can try ASYNCtask to do what you want in the background then change the textviews in onPostExecute()
here you are a link to it:
http://developer.android.com/reference/android/os/AsyncTask.html
Update:
example for AsyncTask
class Name extends AsyncTask<Void, Void, Boolean>{
//you can add variables here to be public in the class
// also you can add a constructor to the class
#Override
protected void onPreExecute() {
super.onPreExecute();
// here the code that you wanna do before starting the thread
// you can delete this method if you want
}
#Override
protected Boolean doInBackground(Void... parms) {
// this is the method that will do the word away from the main thread
// it must exist in the AsyncTask
// this method will return boolean but you should check if it's null or not
}
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
// in this method you can deal with UI
}
}
Related
I have a RecyclerView with ImageViews in each item.
I set onClickListener for the ImageViews in onBindViewHolder as follows:
holder.starIV.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO: logic
}
});
The ripple effect worked fine until I added the following logic to onClick. This logic changes the Drawable for the ImageView.
holder.starIV.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (v.getId() == holder.starIV.getId()) {
ListItem clickedItem = mDataset.get(position);
ListItem updatedItem = new ListItem(clickedItem);
if (clickedItem.getStarState() == STAR_ON) {
updatedItem.setStarState(STAR_OFF);
updatedItem.setStarDrawable(
ContextCompat.getDrawable(
v.getContext(),R.drawable.ic_star_border_24px));
}
else if (clickedItem.getStarState() == STAR_OFF) {
updatedItem.setStarState(STAR_ON);
updatedItem.setStarDrawable(
ContextCompat.getDrawable(
v.getContext(),R.drawable.ic_star_24px));
}
mDataset.set(position,updatedItem);
notifyDataSetChanged();
}
}
});
Now, I get no ripple effect at all. Here's the XML for the ImageView:
<ImageView
android:id="#+id/list_item_star"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingLeft="4dp"
android:paddingRight="16dp"
android:src="#drawable/ic_star_border_24px"
android:clickable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:drawSelectorOnTop="true"
/>
The ripple effect works normally again when i comment out the logic part in onClick.
Have I implemented the above correctly?
What change would you suggest to get the ripple effect working correctly?
EDIT: It appears that changing the Drawable is interfering with the ripple animation. So i moved all the logic to an AsyncTask with a small delay to allow the animation to finish. This seems to work, but I feel this solution is not elegant. Here's the AsyncTask:
class DoLogix extends AsyncTask<Integer, Integer, Void> {
#Override
protected Void doInBackground(Integer... params) {
try{Thread.sleep(125);}catch (Exception e) {}
publishProgress(params[0]);
return null;
}
protected void onProgressUpdate(Integer... val) {
ListItem clickedItem = mDataset.get(val[0]);
ListItem updatedItem = new ListItem(clickedItem);
if (clickedItem.getStarState() == STAR_ON) {
updatedItem.setStarState(STAR_OFF);
updatedItem.setStarDrawable(starBorder);
}
else if (clickedItem.getStarState() == STAR_OFF) {
updatedItem.setStarState(STAR_ON);
updatedItem.setStarDrawable(star);
}
mDataset.set(val[0],updatedItem);
notifyDataSetChanged();
}
}
u can set a ripple drawable as the foreground of ur imageview.
add below code to your parent layout
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
So I have Track.java with its layout that is just suppose to show me updated GPS coordinates with myTextLat and myTextLong. And a MainActivity.java that has a method locationChanged that spits out new GPS data as it becomes available, but for whatever reason my layout is not updating with the new data, despite being able to see new coordinate data coming out of locationChanged in the system out. I can statically set them by doing a settext in onCreateView, but for some reason they will not update through setMyCoords. Can someone help me figure out why the data, when available, is not being passed into my layout? Is there another, better, way to pass data from the activity to objects in a fragment so they are always updatedy? Thanks.
MainActivity's locationChanged
#Override
public void locationChanged(double longitude, double latitude) {
try {
System.out.println("Longitude: " +longitude);
System.out.println("Latitude: " + latitude);
Track f = new Track();
f.setMyCoords(latitude,longitude);
} catch (NullPointerException e) {
}
Track.java
package "";
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class Track extends Fragment {
MiddleMan mCallBack;
TextView myTextLat;
TextView myTextLong;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallBack = (MiddleMan) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement ReqestConnect");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.track_display, container, false);
myTextLat = (TextView) view.findViewById(R.id.textView_lat);
myTextLong = (TextView) view.findViewById(R.id.textView_long);
mCallBack.DisplayHome();
return view;
}
public void setMyCoords(final double slat, final double slong) {
myTextLat.setText(Double.toString(slat));
myTextLong.setText(Double.toString(slong));
}
}
This might also help. Each fragment replaces a framelayout in MainActivity when called. it looks like this.
#Override
public void ShiftView(Object obj) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.activity_main_framelayout, (Fragment) obj);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
With the actual call being something like.
Track f = new Track();
ShiftView(f);
Conclusion
With Joel Min's help I was able to come to the conclusion to my problem. I only have one activity but use several fragments to take on the role of having multiple activities, from the viewpoint of the user:
activity_main.xml
<LinearLayout 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:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
android:orientation="vertical" tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#27b"
android:layout_weight=".04">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/activity_main_framelayout">
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight=".9"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1B5F96"
android:layout_weight=".9"
android:id="#+id/activity_main_status_title"
android:text="#string/activity_main_status_title"
tools:ignore="NestedWeights" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#AD3333"
android:layout_weight=".15"
android:id="#+id/activity_main_status_value"
android:text="#string/activity_main_status_value"/>
</LinearLayout>
</LinearLayout>
The Framelayout eats the vast majority of the display, with ShiftView basically swapping in the fragment's layout by calling on the fragment class, as stated above. The problem? Shifting views is done by the onOptionsitemSelected method where each entry essentially looks like this:
if (id == R.id.action_track_display) {
Track f = new Track();
ShiftView(f);
return true;
Which has been fine for the project up to this point, however, Track.java needs to do something the other classes don't, it needs to receive and retain gps data regardless of where the user is in the app. My menu produces a new Track object, my locationChanged method produces a new Track object every time the location changes [which is a lot of objects], none of the objects are the same and none are connected to MainActivity.java in any way. The result, you get a crashless app that has a Track object's layout visible to the user that never updates and a series of background Track objects that exist for a fraction of a second, each containing one set of gps points. The fix, pretty simple:
MainActivity.java
public class MainActivity extends AppCompatActivity {
Track my_track;
...
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_track_display) {
if (my_track==null) {
my_track = new Track();
}
ShiftView((my_track);
return true;
}
....
#Override
public void locationChanged(final double longitude, final double latitude) {
try {
System.out.println("Main-Longitude: " +longitude);
System.out.println("Main-Latitude: " + latitude);
runOnUiThread(new Runnable() {
#Override
public void run() {
my_track.setMyCoords(latitude,longitude);
}
});
} catch (NullPointerException e) {}
Each fragment replaces a framelayout in MainActivity when called
But you are calling f.setMyCoords(latitude,longitude); after the fragment has been created and returned to the main UI as it is (without setMyCoords applied). So move f.setMyCoords(latitude,longitude); from your locationChanged method to ShitView method. Of course then you will need to have global variables tempLong and tempLat to temporarily store the longitude and latitude values in locationChanged, and access them in ShiftView. Below is the modified code:
private double tempLong, tempLat; //declare it at class level
#Override
public void locationChanged(double longitude, double latitude) {
try {
System.out.println("Longitude: " +longitude);
System.out.println("Latitude: " + latitude);
Track f = new Track();
tempLong = longitude;
tempLat = latitude;
} catch (NullPointerException e) {
}
#Override
public void ShiftView(Object obj) {
(Fragment) obj.setMyCoords(tempLat, tempLong);
//if above line causes error try the line below;
//Track f = (Fragment) obj;
//f.setMyCoords(tempLat, tempLong);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.activity_main_framelayout, (Fragment) obj);
//ft.replace(R.id.activity_main_framelayout, f);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
I cannot guarantee the above code would work because I don't have the full code. But basically you either need to set the longitude and latitude before the fragment transition in main activity occurs, or set a callback mechanism in your setMyCoords method so when it's called it calls back the main activity to update the textviews with new long and lat.
It seems that you are calling setText from NOT ui thread.
Consider calling it in UI thread using smth like this:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
myTextLat.setText(Double.toString(slat));
myTextLong.setText(Double.toString(slong));
}
});
I'm a beginner in android and I've written an activity. It contains a CountDownTimer that counts down from a particular value. It also contains a Button that loads text information and a textview to display count.
Below is the code for Activity1:
public class Screen extends Activity1 implements OnClickListener {
private static final int MILLIS_PER_SECOND = 1000;
private static final int SECONDS_TO_COUNTDOWN = 1;
TextView Time;
int totaltime;
Button startTimer, howTo, pause;
protected CountDownTimer MyTimer;
int PracticeCount;
long tot;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.pushupscreen);
getRefs();
getSpeed();
getCount();
setTotalTime();
startTimer.setOnClickListener(this);
pause.setOnClickListener(this);
}
private void getRefs() {
// Initialize layout resources
Time = (TextView) findViewById(R.id.tvTime);
startTimer = (Button) findViewById(R.id.bStart);
howTo = (Button) findViewById(R.id.btHowTo);
pause = (Button) findViewById(R.id.bPause);
howTo.setOnClickListener(this);
}
private void getTheCount() {
//get count from SharedPreferences
}
private void getSpeed() {
//get speed from SharedPreferences
}
private void setCount(){
totalTime=speed*count;}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (v == startTimer) {
try {
showTimer(time);
} catch (NumberFormatException e) {
// method ignores invalid (non-integer) input and waits
// for something it cant use
}
} else if (v == pause) {
MyTimer.cancel();
Timer.setText("Resume");
} else if (v == howTo) {
//Launch screen containing information
}
}
private void showTimer(long time) {
if (MyTimer != null) {
MyTimer.cancel();
}
MyTimer = new CountDownTimer(tot2, MILLIS_PER_SECOND) {
#Override
public void onTick(long millisUntilFinished) {
tot = millisUntilFinished;
long seconds = millisUntilFinished / 1000;
Time.setText(String.format("%02d", seconds / 60) + ":"
+ String.format("%02d", seconds % 60));
}
#Override
public void onFinish() {
Time.setText("KABOOM!");
}
}.start();
}
}
And here is the layout file for this:
<TextView
android:id="#+id/tvTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:gravity="center"
android:padding="10dip"
android:text="#string/starttime"
android:textSize="60sp" />
<Button
android:id="#+id/bStart"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_above="#+id/tvTime"
android:text="Start" />
<Button
android:id="#+id/bPause"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_above="#+id/tvTime"
android:layout_toRightOf="#+id/btHowTo"
android:text="Pause" />
<TextView
android:id="#+id/tvCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/btHowTo"
android:layout_centerHorizontal="true"
android:layout_marginTop="39dp"
android:text="25"
android:textSize="80sp"
android:textAlignment="center"/>
My questions:
1.How do I create 4 activities that use the same layout and the same timer? Each Activity loads a different content in the textview and a different screen on the click of HowTo button.
2.How can Activity1 be designed to run for 1/4th the time set and pass remaining time to Activity2? Is it possible?
I would really appreciate any help and advice that you can provide. Thanks.
A couple things here.
Its very easy to re-use layouts. In each activity's onCreate you would just call:
setContentView(R.layout.pushupscreen); The pushupscreen.xml file can be shared across all activities this way.
What you probably want to do is persist a timestamp to some common data source for all the activities. This could be a write to a SharedPreferences file: Documentation here. Then as each activity resumes, check how much time has already passed by comparing this timestamp to the current timestamp. You could also pass the timestamp as an extra in the intent to start up the subsequent activities. The documentation for that can be found here and here
You could make a custom control, which is basically a new class which inherits some other control's class (for example a LinearLayout or a RelativeLayout). You could then load a view's XML to your new layout or programmatically create new controls inside your control. More info here:
Custom components in Android
After a 1/4 of your countdown period, you can create and send an Intent to start a new activity in the onTick method. You can also put the remaining 3/4 as a millisecond value (of type long) in an intent extra. You can then obtain this value in the new activity and invoke a custom CountDownTimer child there for the rest of your countdown. Then you can finally execute what you wish after the countdown is done in the onFinish() method.
I'm new to android and having trouble getting some simple things going!
Here is some basic code:
MainActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Lets go ahead and set our buttons up.
ServerButton = (Button)this.findViewById(R.id.ServerButton);
ClientButton = (Button)this.findViewById(R.id.ClientButton);
BTServer = new BluetoothServerService(this, mHandler);
}
#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 ServerButtonClick(View v){
Log.d("BLUETOOTH MAIN ACTIVITY", "It got here");
BTServer.start();
}
private final Handler mHandler = new Handler(){
#Override
public void handleMessage(Message msg){
switch (msg.what){
case HANDLER_CHANGE_SERVER_STATUS:
switch(msg.arg1){
case SERVER_STATUS_OFF:
CURRENT_SERVER_STATUS = SERVER_STATUS_OFF;
//We should probably add some logging, and the change to the button
break;
case SERVER_STATUS_ON:
CURRENT_SERVER_STATUS = SERVER_STATUS_ON;
//Again lets add some logging, etc.
break;
case SERVER_STATUS_ON_CONNECTED:
CURRENT_SERVER_STATUS = SERVER_STATUS_ON_CONNECTED;
//Again lets add some logging, etc.
break;
}
}
return;
}
};
My XML Looks like this:
<Button
android:id="#+id/ServerButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="146dp"
android:layout_marginLeft="64dp"
android:text="Connect to Server" />
<Button
android:id="#+id/ClientButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="28dp"
android:text="Start Server"
android:onClick="ServerButtonClick"/>
In my logs, I see the logs saying it creates the BTServer object, but I can't get any logs to show up when I click the button.
Any suggestions?
Thanks!
define a click action for ClientButton inside onCreate method
ClientButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
}
});
also for ServerButton define a click action like the above as
ServerButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
}
});
For me, I find it's best to have your Activity implement android.view.View.OnClickListener, then set your Button's onClickListener to your Activity.
Here's an example Activity class:
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class Home extends Activity implements OnClickListener
{
#Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_main);
Button serverButton = (Button)this.findViewById (R.id.ServerButton);
serverButton.setOnClickListener (this);
Button clientButton = (Button)this.findViewById (R.id.ClientButton);
clientButton.setOnClickListener (this);
}
#Override
public void onClick(View button)
{
int buttonId = button.getId ();
if (buttonId == R.id.ServerButton)
{
// Do server stuff.
}
if (buttonId == R.id.ClientButton)
{
// Do client stuff.
}
}
}
In Android when you define the android:onClick="someMethod" attribute it only implements the OnClickListener for you.
So it is your responsibility to define the onCLickListener Method and call your ServerButtonClick method from that.
Add this method in your Activity OnCreate Method. It would work.
ServerButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
ServerButtonClick();
}
});
Also, android:onClick method works only if API level > 4 onwards. so if you're targeting Minimum SDK version < 1.6 please do not use it
I'd like to have a TextView display text, and when you click/longclick on it, a textbox should "show up" and allow editing of said text. When you're done editing (onkey enter i suppose) it should revert back to a textview with the updated text...
I'm wondering if it's feasable to implement such a widget or should I hack a workaround? Tips and suggestions are very welcome.
If you need further idea of what I mean, just go to your e.g. (windows) skype profile and see for yourself.
EDIT:
Clarification: I'm specifically asking for a widget or such which is a textview until clicked on, then transforms to an edittext containing the same text; once done editing it transforms back to a textview representing the new changed text. Thats what i mean by "edittext on demand widget".
But I'm hoping to get something better than
public class Widget {
TextView text;
EditText edit;
String textToRepresent;
//...
}
You have a few different options here.
First you will have to register an onClick or onLongClick to the TextView that you want to make interactive. Just make sure that the user knows it's clickable
Then have your onClick function start a DialogFragment. I like to create show functions. Note that you can use the support libraries here to make your app backwards compatible.
private void showDialog() {
MyDialogFragment dialog = new MyDialogFragment();
dialog.show(getSupportFragmentManager(), "dialog");
}
The DialogFragment is pretty straight forward. In your onCreateView you'll inflate the View that you'll want to display to the user. You can alternatively wrap it with a simple AlertDialogBuilder if you don't want to go custom.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.your_dialog_layout);
mTitleEditText = (TextView) view.findViewById(R.id.title);
mTitleEditText.setOnClickListener(this);
return view;
}
After your findViewByIds set your onClickListeners.
The last thing you have to take care of is getting data back into your original TextView.
You can do this by creating a public method in your Activity that you can call from inside of your DialogFragment. Something like this
#Override
public void onClick(View v) {
int clickedId = v.getId();
if (clickedId == mDoneButton.getId()) {
MyActivity activity = (MyActivity)getActivity();
mTitle = mTitleEditText.getText().toString();
activity.setText(mTitle);
dismiss();
}
}
I would recommend using a DialogFragment because it will handle your life cycle nicely.
However, another option would be to create a new Activity themed to be a dialog
<activity android:theme="#android:style/Theme.Dialog" />
Then you can startActivityForResult to display your dialog and then capture your results in onActivityResult
Here is my solution. I just give you the basic one. Create a TextView in front of EditText and two Button OK,Cancel (You can change to ImageButton like Skype). Change the visiblity of two view. The code is so simple without comment. You can add some null checking according your logic.
public class CompoundTextView extends RelativeLayout implements OnClickListener {
private EditText edt;
private TextView txt;
RelativeLayout layout;
public SkypeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
edt = (EditText) findViewById(R.id.edt);
txt = (TextView) findViewById(R.id.txt_name);
layout = (RelativeLayout) findViewById(R.id.layout);
Button ok = (Button) findViewById(R.id.ok_btn);
Button cancel = (Button) findViewById(R.id.cancel_btn);
ok.setOnClickListener(this);
cancel.setOnClickListener(this);
txt.setOnClickListener(this);
}
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.ok_btn:
String editString = edt.getText().toString();
txt.setText(editString);
layout.setVisibility(View.INVISIBLE);
txt.setVisibility(View.VISIBLE);
break;
case R.id.cancel_btn:
layout.setVisibility(View.INVISIBLE);
txt.setVisibility(View.VISIBLE);
break;
case R.id.txt_name:
txt.setVisibility(View.INVISIBLE);
layout.setVisibility(View.VISIBLE);
break;
}
}
}
Create a XML skypetextview. You can customize font and background to make it's prettier.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="#+id/txt_name"
android:layout_width="fill_parent"
android:layout_height="100dp"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:background="#ff0000" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
android:id="#+id/layout" >
<EditText
android:id="#+id/edt"
android:layout_width="270dp"
android:layout_height="100dp" />
<Button
android:id="#+id/ok_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/edt"
android:text="OK" />
<Button
android:id="#+id/cancel_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/ok_btn"
android:layout_toRightOf="#id/edt"
android:text="Cancel" />
</RelativeLayout>
</RelativeLayout>
add (or include) this view to the layout you want.
Example :
public class TestActivity extends Activity {
SkypeTextView test;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflate = getLayoutInflater();
test = (SkypeTextView ) inflate.inflate(R.layout.compound_text_view,
null);
setContentView(test);
}
PS: i forgot. You should add some underline format for your textview in order to make user notice it clickable
Let a EditText change its background based on its state(Editable or Frozen). Set a background selector that does this.
Use this selector xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_focused="true" android:drawable="#android:drawable/edit_text"/>
<item android:drawable="#android:drawable/screen_background_light_transparent"/>
</selector>
Like I said on thursday... Yul was pretty close but not quite close. He did have a general same idea but (theoretically) rushed into code too early ;)
The TextBoxOnDemand code supplied below is production-ready. The idea is similar to what I wanted to avoid in the OP and what Yul suggested, but with optimal implementation (using a ViewSwitcher instead of a RelativeLayout for instance)
I gathered the resources needed for this in the following articles:
Creating custom view from xml
Declaring a custom android UI element using XML
Defining custom attrs
How to pass custom component parameters in java and xml
http://kevindion.com/2011/01/custom-xml-attributes-for-android-widgets/
and decided to post them here because the official Google "training" docs are useless and are either obsolete (deprecated) or do not cover what I needed. I hope you don't mind me claiming my own bounty, but this is the solution I wanted (and expected, ergo the bounty).
I guess the code will have to do ;)
TextBoxOnDemand.java:
package com.skype.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.text.SpannableString;
import android.text.style.UnderlineSpan;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnHoverListener;
import android.view.View.OnLongClickListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.ViewSwitcher;
import com.skype.ref.R;
import com.skype.ref.RemoteKeys;
public class TextBoxOnDemand extends ViewSwitcher implements OnClickListener, OnLongClickListener, OnFocusChangeListener, OnHoverListener,
OnEditorActionListener
{
public static final String LOGTAG = "TextBoxOnDemand";
private View btmGuard;
private ImageButton cancel, accept;
private EditText editor;
private RelativeLayout editorLayout;
private TextView face;
private String hint = new String();
private boolean inEditMode = false; //normally this is in textview mode
private boolean inputReady = false;
private String ourData = new String();
private String prefillData = new String();
private String tag = new String(); //usually tag is empty.
private View topGuard;
private int autoLinkMask;// = Linkify.EMAIL_ADDRESSES; //Linkify.ALL;
private ColorStateList textColor, hintColor = null;
public TextBoxOnDemand(Context context)
{
super(context);
build(context);
setEditable(false); //init
}
public TextBoxOnDemand(Context context, AttributeSet attrs)
{
super(context, attrs);
build(context);
init(context, attrs);
setEditable(false); //init
}
public String getPrefillData()
{
return prefillData;
}
public String getTag()
{
return tag;
}
public String getText()
{
Log.d(LOGTAG, "getText() returning '" + ourData + "'");
return ourData;
}
public boolean hasPrefillData()
{
return prefillData.isEmpty();
}
public boolean isEditable()
{
Log.d(LOGTAG, "isEditable() returning " + inEditMode);
return inEditMode;
}
#Override
public void onClick(View v)
{
Log.d(LOGTAG, "onClick(" + v + ")");
if (inEditMode)
{
if (v.equals(accept))
{
if (editor.getEditableText().length() == 0 || editor.getEditableText().length() > 5)
ourData = editor.getEditableText().toString();
setEditable(false);
} else if (v.equals(cancel))
{
setEditable(false);
}
}
}
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
{
// Log.d(LOGTAG, "onEditorAction(" + v + ", " + actionId + ", " + event + ") fired!");
Log.d(LOGTAG, "onEditorAction() fired, inputReady = " + inputReady);
if (editor.getEditableText().length() > 0 && editor.getEditableText().length() < (prefillData.length() + 2)) return true; //the user needs to enter something
if (inputReady && (event.getKeyCode() == RemoteKeys.ENTER.keycode() || event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) //always is
{
if (editor.getEditableText().length() > prefillData.length() || editor.getEditableText().length() == 0)
ourData = editor.getEditableText().toString();
setEditable(false);
return false;
}
if ((editor.getEditableText().toString().compareToIgnoreCase(ourData) == 0 || editor.getEditableText().toString()
.compareToIgnoreCase(prefillData) == 0)
&& !inputReady) //means we didn't just keep on holding enter
return true;
else
inputReady = true;
return true;
}
#Override
public void onFocusChange(View v, boolean hasFocus)
{
Log.d(LOGTAG, "onFocusChange(" + v + ", " + hasFocus + ")\tinEditMode = " + inEditMode);
if (inEditMode)
{
if (hasFocus && (v.equals(topGuard) || v.equals(btmGuard)))
{
setEditable(false);
requestFocus();
}
if (hasFocus && (v.equals(editor) || v.equals(accept) || v.equals(cancel)))
{
//do nothing, you should be able to browse freely here
if (ourData.isEmpty() && editor.getEditableText().length() < prefillData.length())
{
Log.d(LOGTAG, "adding prefill, before = " + editor.getEditableText());
editor.setText("");
editor.append(prefillData);
Log.d(LOGTAG, "now is = " + editor.getEditableText());
}
}
} else
{
String text = (ourData.isEmpty()) ? hint : ourData;
ColorStateList color;
if (hintColor != null && ourData.isEmpty())
color = hintColor;
else
color = textColor;
face.setTextColor(color);
if (hasFocus)
{
SpannableString ss = new SpannableString(text);
ss.setSpan(new UnderlineSpan(), 0, text.length(), 0);
face.setText(ss);
} else
face.setText(text);
}
}
#Override
public boolean onHover(View v, MotionEvent event)
{
// Log.d(LOGTAG, "onHover()");
String text = (ourData.isEmpty()) ? hint : ourData;
ColorStateList color;
if (hintColor != null && ourData.isEmpty())
color = hintColor;
else
color = textColor;
face.setTextColor(color);
switch (event.getAction())
{
case MotionEvent.ACTION_HOVER_ENTER:
SpannableString ss = new SpannableString(text);
ss.setSpan(new UnderlineSpan(), 0, text.length(), 0);
face.setText(ss);
break;
case MotionEvent.ACTION_HOVER_EXIT:
face.setText(text);
break;
}
return true;
}
#Override
public boolean onLongClick(View v)
{
Log.d(LOGTAG, "onLongClick()\tinEditMode = " + inEditMode);
if (!inEditMode) //implies that getDisplayedChild() == 0, meaning the textview
{
setEditable(true);
return true;
} else
return false;
}
public void setEditable(boolean value)
{
Log.d(LOGTAG, "setEditable(" + value + ")");
inEditMode = value;
if (inEditMode)
{
//display the editorLayout
face.setOnLongClickListener(null);
face.setOnHoverListener(null);
face.setOnFocusChangeListener(null); //because of GC.
face.setOnClickListener(null);
face.setVisibility(View.GONE);
setDisplayedChild(1);
editorLayout.setVisibility(View.VISIBLE);
editor.setOnFocusChangeListener(this);
editor.setOnEditorActionListener(this);
cancel.setOnClickListener(this);
accept.setOnClickListener(this);
accept.setOnFocusChangeListener(this);
cancel.setOnFocusChangeListener(this);
} else
{
editor.setOnFocusChangeListener(null);
editor.setOnEditorActionListener(null);
cancel.setOnClickListener(null);
accept.setOnClickListener(null);
accept.setOnFocusChangeListener(null);
cancel.setOnFocusChangeListener(null);
editorLayout.setVisibility(View.GONE);
setDisplayedChild(0);
face.setVisibility(View.VISIBLE);
face.setOnLongClickListener(this);
face.setOnHoverListener(this);
face.setOnFocusChangeListener(this);
face.setOnClickListener(this);
face.setFocusable(true);
face.setFocusableInTouchMode(true);
}
updateViews();
}
#Override
public void setNextFocusDownId(int nextFocusDownId)
{
super.setNextFocusDownId(nextFocusDownId);
face.setNextFocusDownId(nextFocusDownId);
// editor.setNextFocusDownId(nextFocusDownId);
accept.setNextFocusDownId(nextFocusDownId);
cancel.setNextFocusDownId(nextFocusDownId);
}
#Override
public void setNextFocusForwardId(int nextFocusForwardId)
{
super.setNextFocusForwardId(nextFocusForwardId);
face.setNextFocusForwardId(nextFocusForwardId);
editor.setNextFocusForwardId(nextFocusForwardId);
}
#Override
public void setNextFocusLeftId(int nextFocusLeftId)
{
super.setNextFocusLeftId(nextFocusLeftId);
face.setNextFocusLeftId(nextFocusLeftId);
editor.setNextFocusLeftId(nextFocusLeftId);
}
#Override
public void setNextFocusRightId(int nextFocusRightId)
{
super.setNextFocusRightId(nextFocusRightId);
face.setNextFocusRightId(nextFocusRightId);
cancel.setNextFocusRightId(nextFocusRightId);
}
#Override
public void setNextFocusUpId(int nextFocusUpId)
{
super.setNextFocusUpId(nextFocusUpId);
face.setNextFocusUpId(nextFocusUpId);
// editor.setNextFocusUpId(nextFocusUpId);
accept.setNextFocusUpId(nextFocusUpId);
cancel.setNextFocusUpId(nextFocusUpId);
}
public void setPrefillData(String prefillData)
{
this.prefillData = new String(prefillData);
}
public String setTag()
{
return tag;
}
public void setText(String text)
{
Log.d(LOGTAG, "setText(" + text + ")");
ourData = text;
updateViews();
}
private void build(Context context)
{
Log.d(LOGTAG, "build()");
addView(View.inflate(context, R.layout.textboxondemand, null));
setFocusable(true);
setFocusableInTouchMode(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setOnFocusChangeListener(this);
setOnLongClickListener(this);
face = (TextView) findViewById(R.id.TBOD_textview);
editorLayout = (RelativeLayout) findViewById(R.id.TBOD_layout);
editor = (EditText) findViewById(R.id.TBOD_edittext);
accept = (ImageButton) findViewById(R.id.TBOD_accept);
cancel = (ImageButton) findViewById(R.id.TBOD_cancel);
topGuard = (View) findViewById(R.id.TBOD_top);
btmGuard = (View) findViewById(R.id.TBOD_bottom);
face.setFocusable(true);
face.setFocusableInTouchMode(true);
face.setOnLongClickListener(this);
face.setOnHoverListener(this);
face.setOnFocusChangeListener(this);
face.setOnClickListener(this);
editor.setOnFocusChangeListener(this);
editor.setOnEditorActionListener(this);
editor.setHint(hint);
editor.setFocusable(true);
editor.setFocusableInTouchMode(true);
accept.setOnClickListener(this);
accept.setOnFocusChangeListener(this);
accept.setFocusable(true);
cancel.setFocusable(true);
cancel.setOnFocusChangeListener(this);
cancel.setOnClickListener(this);
topGuard.setFocusable(true);
topGuard.setOnFocusChangeListener(this);
btmGuard.setFocusable(true);
btmGuard.setOnFocusChangeListener(this);
editor.setNextFocusRightId(R.id.TBOD_accept);
editor.setNextFocusDownId(R.id.TBOD_bottom);
editor.setNextFocusUpId(R.id.TBOD_top);
accept.setNextFocusLeftId(R.id.TBOD_edittext);
accept.setNextFocusRightId(R.id.TBOD_cancel);
cancel.setNextFocusLeftId(R.id.TBOD_accept);
}
private void init(Context context, AttributeSet attrs)
{
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextBoxOnDemand);
//Use a
Log.d(LOGTAG, "init()");
if (a == null) Log.d(LOGTAG, "Did you include 'xmlns:app=\"http://schemas.android.com/apk/res-auto\"' in your root layout?");
final int N = a.getIndexCount();
for (int i = 0; i < N; ++i)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.TextBoxOnDemand_android_hint:
hint = new String(a.getString(attr));
editor.setHint(a.getString(attr));
break;
case R.styleable.TextBoxOnDemand_android_text:
ourData = new String(a.getString(attr));
break;
case R.styleable.TextBoxOnDemand_android_inputType:
int inputType = a.getInt(attr, -1);
if (inputType != -1) editor.setInputType(inputType);
break;
case R.styleable.TextBoxOnDemand_android_textColor:
textColor = a.getColorStateList(attr);
face.setTextColor(textColor);
break;
case R.styleable.TextBoxOnDemand_android_linksClickable:
face.setLinksClickable(a.getBoolean(attr, true));
break;
case R.styleable.TextBoxOnDemand_android_textColorHint:
hintColor = a.getColorStateList(attr);
break;
case R.styleable.TextBoxOnDemand_android_autoLink:
autoLinkMask = a.getInt(attr, 0);
face.setAutoLinkMask(autoLinkMask);
break;
default:
Log.d(LOGTAG, "Skipping attribute " + attr);
}
}
//Don't forget this
a.recycle();
}
private void updateViews()
{
Log.d(LOGTAG, "updateViews()");
// if (getDisplayedChild() == 0) //first child - textview
if (!inEditMode) //first child - textview
{
if (ourData.isEmpty())
{
if (hintColor != null) face.setTextColor(hintColor);
face.setText(hint);
} else
{
face.setTextColor(textColor);
face.setText(ourData);
}
face.setFocusable(true);
face.setFocusableInTouchMode(true);
face.setAutoLinkMask(autoLinkMask);
} else
{ //second child - edittext
editor.setFocusable(true);
editor.setFocusableInTouchMode(true);
if (ourData.startsWith(prefillData) || ourData.length() >= prefillData.length())
editor.setText("");
else
editor.setText(prefillData);
editor.append(ourData);
inputReady = false;
editor.requestFocus();
}
}
public void setAutoLinkMask(LinkifyEnum linkifyEnumConstant)
{
switch (linkifyEnumConstant)
{
case ALL:
autoLinkMask = Linkify.ALL;
break;
case EMAIL_ADDRESSES:
autoLinkMask = Linkify.EMAIL_ADDRESSES;
break;
case MAP_ADDRESSES:
autoLinkMask = Linkify.MAP_ADDRESSES;
break;
case PHONE_NUMBERS:
autoLinkMask = Linkify.PHONE_NUMBERS;
break;
case WEB_URLS:
autoLinkMask = Linkify.WEB_URLS;
break;
case NONE:
default:
autoLinkMask = 0;
break;
}
//set it now
face.setAutoLinkMask(autoLinkMask);
}
public enum LinkifyEnum
{
ALL, EMAIL_ADDRESSES, MAP_ADDRESSES, PHONE_NUMBERS, WEB_URLS, NONE
};
}
I'm still working out some focus-related issues but this works as intended. When I use onFocuslistener 1, you can't focus from one TextBox to the other; when the textbox itself is focusable, I can focus from one to the other just fine, but I cannot inter-focus thru children and thus can't focus on the edittext to type.
the XML file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/TBOD_textview"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:autoLink="email"
android:focusable="true"
android:focusableInTouchMode="true"
android:linksClickable="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<RelativeLayout
android:id="#+id/TBOD_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<EditText
android:id="#+id/TBOD_edittext"
android:layout_width="300dp"
android:layout_height="30dp"
android:layout_below="#+id/TBOD_textview"
android:focusable="true"
android:focusableInTouchMode="true"
android:imeOptions="actionDone"
android:inputType="none"
android:maxLines="1"
android:padding="2dp"
android:singleLine="true"
android:textColor="#android:color/black"
android:textSize="14dp" />
<ImageButton
android:id="#+id/TBOD_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/TBOD_edittext"
android:layout_marginLeft="15dp"
android:layout_toRightOf="#+id/TBOD_edittext"
android:background="#drawable/button_accept_selector" />
<ImageButton
android:id="#+id/TBOD_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/TBOD_edittext"
android:layout_marginLeft="5dp"
android:layout_toRightOf="#+id/TBOD_accept"
android:background="#drawable/button_cancel_selector" />
<View
android:id="#+id/TBOD_top"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_alignParentTop="true"
android:background="#android:color/transparent" />
<View
android:id="#+id/TBOD_bottom"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_alignParentBottom="true"
android:background="#android:color/transparent" />
</RelativeLayout>
</RelativeLayout>
and finally, the attrs.xml file:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TextBoxOnDemand">
<attr name="android:text" />
<attr name="android:inputType" />
<attr name="android:hint" />
<attr name="android:textColor" />
<attr name="android:textColorHint" />
<attr name="android:linksClickable" />
<attr name="android:autoLink" />
</declare-styleable>
</resources>
This is how I used it in my main xml (after including the required namespace add):
<com.shark.widget.TextBoxOnDemand
android:id="#+id/profile_email2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/profile_skypename"
android:layout_below="#+id/profile_email_placeholder"
android:hint="#string/add_email"
android:inputType="textEmailAddress"
android:textColor="#android:color/white"
android:textColorHint="#color/skype_blue" />
EDIT: I've debugged the focus issues. It turns out that giving focus to children is difficult unless you call
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Which kinda remedies the issue but still doesn't solve it. After some while of playing around with the onFocusChange() listener still trying to get the perfect behaviour, I threw in the towel and put in added two focus guards. I realized I cannot track the loss of focus only on my container (due to it never receiving focus) but I might as well track the idea of wanting to move away from the edit field... So i went the dirty route and added two invisible bar-like views to sandwitch the edittext in between. Once they got the focus, I could hide the component and ensure they transition properly.
And there it is, now it works as it should. Thanks to all who participated.
EDIT3: final polished version, i dumped the custom tags because they simply don't work reliably enough. Lesson to be learned: if there is an android tag for something, don't bother cloning it.