At the moment I am working on a project for a graphical user interface on an Android tablet. On one screen I have several buttons, which I want to make a RelativeLayout visible/invisible with.
I tried using an onClickListener, but in the inner method onClick doesn't support non final variables, which I use to select each button and RelativeLayout.
The GUI is built dynamically, as its whole structure depends on the data it gets fed via an XML file. Also the RelativeLayout uses TextViews which receive an update of their textes (sensordata like temperature and humidity), which is why a dynamic approach is used.
Could you give me some ideas for a workaround around that problem? Help is appreciated. If the stated information is not enough for you, just ask and I will give you more details.
At the moment I tried this:
private void setSensorPointOnClick(final ObjectView currentObjectView, final String currentLinkName)
{
for(int i=0; i<listofSensorDeviceButtons.size(); i++ )
{
listofSensorDeviceButtons.get(i).setOnClickListener(
new View.OnClickListener()
{
public void onClick(View v)
{
arrayClickedButton[i] = listofSensorDeviceButtons.get(i);
mainFrameLayout.removeAllViews();
stepToObject(currentObjectView, currentObjectView.getName());
}
}
);
}
}
either use fields which hold the views that you want to hide/show (and use them in the onClickListener) , or use findViewById within the event handling .
another alternative would be to create another view variable which is final , that will be set just before setting the onClickListener , to hold the reference of the newly created view .
so , in short , the possible solutions i wrote are:
use fields .
use findViewById
use an additional final variable .
Related
I came from HTML/JavaScript world to Android development and there is one thing I cannot still figure out.
Is there any templating system available for Android development in Java?
I mean, when I create layout in HTML/Javascript, I use Mustache.js to define the template once and I use it in the app to dynamically create the final layout in the application.
In Java, when I want to create for example Buttons in loop, I do somethink like this:
for (Int i = 0; i <= 5; i++ {
Button button = new Button(mContext);
button.set_some_parameter_here()
button.set_some_other_parameter_here()
etc()...
layout.addView(button);
}
Is there any way how I predefine the View (or entire layout) in XML (with variables to be replaced in Java loop like {text} of the Button) and afterwards I only insert this template in loop to the final layout viewgroup?
I know how to define static layout in XML, I am asking if there is any way to define XML for future dynamic print.
Thank you.
Jan
In case you want a list of buttons dynamically you can use a recyclerview of buttons and handle each button as you want.
Check this RecyclerView Button Android Studio Example Get Click Position
And if you want to reuse an xml template you can use include keyword,
here is the reference Re-using layouts with
Another way to use predefined layouts/views, you can create your custom view (button) and set it's attributes then use it.Check the example below -You can extend Button and set it's attributes.
public class CustomButton extends Button {
// Set your parameters
}
Android Studio provides powerful refactoring, for example Rename. I can use it to change the name of variables, fields, parameters, however I cannot seem to find a way to rename a type. For example:
LinearLayout layout = (LinearLayout) v.findViewById(....);
// ........
// A bunch of code using `layout` many times
How can I quickly refactor LinearLayout to RelativeLayout and have it be applied to the rest of the code too? And can I do the same thing for fields?
Short Answer
The function you are looking for is Type Migration!
A Type Migration can be performed by right clicking on the type of a variable or field and then selecting Refactor -> Type Migration. Alternatively you can use these keyboard shortcuts:
On a Mac: Shift + ⌘ + F6
On Windows: Shift + Ctrl + F6
Just choose the type you want to migrate to, click refactor and Android Studio starts working its magic!
Long and more detailed Answer
You seem to misunderstand what Rename actually does.
Rename can be used to literally rename elements. So you can change the name of a variable, parameter, method or class with it. For example if you have a class named Foo and you want to change its name to Bar you can do that easily with Rename.
But you cannot rename LinearLayout since it is framework class and those of course cannot be modified. This however shouldn't be a problem at all because you don't actually want to rename LinearLayout, do you? What you actually want to do is change the type from LinearLayout to RelativeLayout. And there is another very useful refactor feature to do exactly that, it is called Type Migration.
You can perform a Type Migration by right clicking on any variable whose type you want to exchange for another and then selecting Refactor -> Type Migration. After that a Dialog pops up and you can enter the type you want to migrate to, in your case RelativeLayout. Then just click Refactor and Android Studio starts working its magic. There may be an additional popup window informing you of all the things in your code which cannot be migrated automatically. Just scan through the list of conflicts and when you are done hit Ignore and fix those conflicts manually.
Here is an example of Type Migration at work. I started out with this code:
private LinearLayout mLayout;
private void doStuff(ViewGroup container) {
LinearLayout layout = (LinearLayout) container.findViewById(0);
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
...
}
});
mLayout = layout;
fooTheBar(layout);
}
private void fooTheBar(LinearLayout layout) {
...
}
Now I performed a Type Migration to RelativeLayout on the local variable layout in doStuff(). The result looks like this:
private RelativeLayout mLayout;
private void doStuff(ViewGroup container) {
// Here is the only conflict which could not be refactored automatically.
// I had to change the cast to RelativeLayout manually.
RelativeLayout layout = (LinearLayout) container.findViewById(R.id.linearLayout);
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
...
}
});
mLayout = layout;
fooTheBar(layout);
}
private void fooTheBar(RelativeLayout layout) {
...
}
As you can see Type Migration did excellent work. The type of the field and even the type of the parameter of fooTheBar() was changed to RelativeLayout. There was only one conflict. Android Studio couldn't automatically change the type of the cast at the very top of doStuff(). I had to fix that manually. As I mentioned earlier I was warned about this conflict while performing the refactoring.
You could of course ask yourself why it could change the type of the field and the parameter automatically but couldn't change the type of a cast, but if you think about it that actually makes a lot of sense:
The part of the code it couldn't migrate automatically was (LinearLayout) container.findViewById(R.id.linearLayout). This method of course looks for a View with the id R.id.linearLayout. This View might be defined in a layout xml or it may be added to the container dynamically at runtime but in any case it is not something which can just be refactored automatically without the risk of breaking functionality. It is something only the developer can decide how to handle and that's why you are warned about it.
For Type Migration in Kotlin file:
Say you want to change the input parameter from String to Int in the following function.
fun getValue(key: String){
}
Place cursor on the type you want to change (String in this case)
Right click > Refactor > Change
Signature. Or use shortcuts below for this step:
Mac: ⌘ + F6
Windows: Ctrl + F6 (not tested)
Completing step 2 will open this window, click on row highlighted with blue:
Replace type with whatever you want and click Refactor
Keeping things short and simple, which is better for managing android click events when working with buttons?
Assigning it to an ID in the XML Construct lines?
android:id="#+id/ButtonEventName"
and working with it by detecting press event via Java?
Taken from the login activity template based in android studio:
Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
mEmailSignInButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
attemptLogin();
}
});
or assigning the button to an onclick method:
android:onClick="EventName"
the integrate Java side:
public void EventName(){}
Which is more effective and reliable and recommended when working with button events?
Both methods have their good and bad sides. I don't know which one is more effective though.
Using id + onClickListener is more complex code, but it gives you opportunity to do run-time check for errors. In your example that would mean additional null check.
Second approach is much simpler to read, but if you don't have EventName method app will crash
I prefer second one because it is simpler, and it is always easier to follow logic and find errors in less code.
I also have to add that while first solution does provide means of avoiding app crash, at the end that does not matter too much because if findViewById returns null you basically have same buggy, code that is in mismatch with layout xml and adding null check (that should be superfluous in final working app) just additionally pollutes the code.
Point 1 -
Creating it in code allow you to shield method access by making the method private, whereas doing it the xml way leads to exposure of the method.
Point 2 - android:onClick is for API level 4 onwards, so if you're targeting < 1.6, then you can't use it.
Otherwise as per my understanding, these 2 are similar.
It is really up to you, but keep in mind that android:onClick="EventName" will not work if you define it in an xml that will be inflated as part of a Fragment and you try to receive the button press on the Fragment itself. The onClick property only searches Activities for the method name you specified.
I would recommend you look into ButterKnife
I am trying to learn Android app programming by building an app to keep score in Hearts. The data for the game itself is arranged in the following hierarchy:
Game represents the whole game
Vector of Round objects representing each hand in the game.
Vector of BoxScore objects representing each box score in a hand.
This data is presented in a ScoreTableActivity by a TableLayout, where each TableRow contains a marker cell, a cell for each BoxScore object, and a cell which indicates whether the hand's total score is correct. The final row of the table shows each player's total score, by adding up the box scores in each column.
I have a method drawScoreTable() which is called during the activity's onCreate() method, and it is working as expected. While creating the cells for box scores, I have the following to catch clicks on those cells:
TextView txtScore = ScoreTableCellFactory.getBoxScoreCell( this, oScore ) ;
txtScore.setOnClickListener(this) ;
rowRound.addView( txtScore ) ; // rowRound is a TableRow.
The ScoreTableActivity itself implements OnClickListener in order to support this; only the box scores are clickable. The activity's onClick() method is as follows:
public void onClick( View oClicked )
{
// A reference to the score object is built into the view's tag.
BoxScore oScore = (BoxScore)oClicked.getTag() ;
// Create the dialog where the user modifies the box score.
BoxScoreEditorDialogFragment fragBoxScoreDialog = new BoxScoreEditorDialogFragment() ;
fragBoxScoreDialog.setBoxScore(oScore) ;
fragBoxScoreDialog.setRules(m_oGame.getRules()) ;
fragBoxScoreDialog.show(getFragmentManager(), "fragBoxScore") ;
// We passed the BoxScore object across to the editor dialog by
// reference (It's Java, after all), so we should be able to
// update the text of the box score cell by simply re-examining
// the data in that BoxScore object.
((TextView)oClicked).setText(Integer.toString(oScore.getScore())) ;
// And it's at this point that something else is clearly needed.
}
Other answers on this site have suggested that the setText() method is enough to convince the renderer to refresh the cell, but it is not. With the code as it is above, the cell is not refreshed until the next time that the cell is clicked.
I have tried using the invalidate() method on the cell itself, its parent row, and the entire TableLayout, but none of these had any effect. I even tried using the removeAllViews() method and then calling drawScoreTable() again; even that didn't update the screen until after the next click event was caught.
If I tilt the tablet to a new orientation (from portrait to landscape or vice versa) then the entire activity is recreated and the new table shows all the correct data. I would rather not resort to completely destroying and rebuilding the whole table but I thought that's what I was doing with removeAllViews() and even that didn't work.
EDIT: Forceful solution found.
Part of the problem stems from the fact that the data update comes from the dialog. This is a separate arena from the basic activity, so the dialog needs to trigger something when it exits.
My code is a bit more specialized but I've created a general example below to give you a context-free idea of what's going on. It's actually based on the official Android reference for "Dialogs", which I unfortunately read only after posting this question.
Step 1: Create a custom listener class for the dialog.
/**
* Callers of this dialog must implement this interface to catch the events
* that are returned from it.
*/
public interface Listener
{
public void onDialogCommit( MyDialogClass fragDialog ) ;
}
Step 2: Implement the listener in your base activity.
At the head of your main activity class:
public class MyBaseActivity
extends Activity
implements OnClickListener, MyDialogClass.Listener
I've kept the OnClickListener here because my code also catches the clicks that trigger the dialog's creation. If you handle this with inline inner classes, then you don't need the OnClickListener in your implements clause.
Step 3: Implement the listener's interface in your base activity.
This is the part that's left out of the official Android example -- What do you do put into this listener method? Well, the answer is surprisingly awful.
public void onDialogCommit( MyDialogClass oDialog )
{
TableLayout oLayout = (TableLayout)(findViewById(R.id.tbl_MyTableLayout)) ;
// This is where things still seem more ugly than they should.
oLayout.removeAllViews() ;
this.recreateEverything() ; // assumes you've written a method for this
}
Surprises
Even after creating this new interface-and-listener model, using the invalidate() and requestLayout() methods still wasn't enough. I had to removeAllViews() and recall the method that redraws the whole activity. I still believe that, surely, there's a more efficient way of doing this, but I haven't yet found it.
Let me preface my question by saying I have done a lot of research in creating dynamic buttons within an android app, and most are simply wrong or have a different view of dynamic than I do. If I missed something then just post the link and I'll check it out.
What I'm looking for is a way to create a button within my app based on information I gather from internet sources. For instance, when someone creates a post on a forums that I care to see, the app will find this, parse it for me, and return some info. Since I can't fit all this info on screen for each post that shows up, I want to create a button dynamically that previews this info. Such as name of user, date, and short description (as a preview that by clicking, will give all of the inforamtion in a separate activity). For the sake of this post, lets pretend I get this info from a text-entry location (not from an actual internt forum post).
First and foremost, how do I create the button dynamically? The other half of my question is less important to me. I would like to do this programmatically. Links to tutorials are great.
Secondly, and less important... Once I have created this button dynamically, how can I get custom views of the button based on a predictable format.
If anything is unclear, just ask and I'll try to clarify. Thanks for all your help!
In my opinion the right approach is to have an Adapter that will map the data to a certain view (a button in your case).
What adapter you choose will be depending on how you decide to fetch and store the data from internet.
When there are new posts you will be adding them to the data source (a database, a list, etc...) and you will call notifyDataSetChanged which will refresh the list, dynamically creating as many views as needed to display all the data.
I think this answers your question. The idea is to programatically create a button, and then add it to the current layout. Somethiug like this:
Button newButton = new Button(this);
newButton.setText("Click Me");
newButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
...
}
});
container.addView(newButton);
Where container is the layout that will hold the button (i.e. will become it's parent). You can also add layout settings to the button if desired.
Well, you can create a new Button, and set an onClickListener as so:
Button button = new Button(context);
button.setText("New Button");
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
If you have anything else you need to set, such as an ID, you can call the method as you wish. You will need then add it to your layout as so:
LinearLayout layout = (LinearLayout)findViewById(R.id.linearLayout);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
button.setLayoutParams(params);
layout.addView(button);
There is of course more you can do with it, but that should get you started, and anything else you need you should be able to find by perusing the docs.
If you need help with anything more specifically, then just comment and I will attempt to elaborate.