I have created some views in xml which I am using as lines in my application. Every view has the exact same properties.
Example:
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#000" />
I want to change the color of all these views simultaneously when the app opens.
Creating ids and then using findviewbyid in java for all of them would not be very efficient. What is the best way to achieve this?
I want them to have the same color all the time.
There are no XML shortcuts in Android for this since you cannot create color changing animations like you described. Now, if you want "efficiency" in terms of clean code while using Java; Take a look at Butterknife, a library that for binding views by using Java annotations in your code. It makes your code very declarative and you can avoid using findViewById, amongst other features.
Dependencies
Add to your project build.gradle file;
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
Add to your app module build.gradle file;
compile 'com.jakewharton:butterknife:8.4.0'
apt 'com.jakewharton:butterknife-compiler:8.4.0'
In the Activity (or View or Fragment)
To make Butterknife bind views we'll request it to bind our Activity, onCreate is a good place. Then we'll add fields for Butterknife to bind the views to, in this case as a List of views., although you can also bind the views one by one. Then we'll just change their background for this example.
public class MyActivity extends Activity {
#BindViews({R.id.view1, R.id.view2, R.id.view3})
List<View> views;
public void onCreate(...) {
...
setContentView(...);
Butterknife.bind(this);
for(View v : views) {
v.setBackgroundColor(ContextCompat.getColor(this, R.color.colorAccent));
}
}
}
Or if you need to distinguish between the views, instead of having them in a list;
public class MyActivity extends Activity {
#BindView(R.id.view1)
View view1;
#BindView(R.id.view2)
View view2;
#BindView(R.id.view3)
View view3;
public void onCreate(...) {
...
setContentView(...);
Butterknife.bind(this);
view1.setBackgroundColor(ContextCompat.getColor(this, R.color.color1));
view2.setBackgroundColor(ContextCompat.getColor(this, R.color.color2));
view3.setBackgroundColor(ContextCompat.getColor(this, R.color.color3));
}
}
Related
I just got a hold of Butterknife and have been trying my best to standardize all of my 'OnClick's to be bound via Butterknife.
I have found though, that it's difficult to follow Butterknife's standard binding pattern when dynamically populating views (via adapters for example) since the individual views don't have id's
#OnClick(What Do I put here if I have no ID?)
public void OnClickMethod(View view) {
//Body
}
Specifically, I'm having problems adding onClicks to views that are part of a TabLayout. I know I can use the built in
TabLayout.setOnTabSelectedListener()
But ideally I'd like to be consistent in binding all forms of onClick via Butterknife. Is there a clean way of doing this?
Set an id in res/values/ids.xml like :
<item name="my_view" type="id"/>
And then add the id to the view :
myView.setId(R.id.my_view);
#OnClick(R.id.my_view)
public void OnClickMethod(View view) {
//Body
}
As we all know,if we want to use Butterknife to bind click event onto a normal view,we just need to declare the below method:
#onClick(R.id.yourview)
public void submit(){
// do your things
}
But,how can I bind click event onto a view in a layout.
Answer is the same. You can do the same way:
#OnClick(R.id.yourview)
public void submit(){
// do your things
}
When you have a inside a XML layout definition Android inflate all the content into a viewgroup replacing the include.
So, the view id is valid to do a reference like the code above.
I am having trouble grasping a certain concept in Android UI design. The book I am referring to first uses the usual technique that Java programmers use to create UIs and that is to to create containers and add UI components to them and nest them as necessary.
Now, the book introduces a new concept where the entire UI was created using an XML file. The code is pasted below:
package com.oreilly.android.intro;
import android.app.Activity;
import android.os.Bundle;
/**
* Android UI demo program
*/
public class AndroidDemo extends Activity {
private LinearLayout root;
#Override public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.main);
root = (LinearLayout) findViewById(R.id.root);
}
}
so basically, I can use any of them ?
Simple answer,yes, you can use either approach. However, there are some limitations to each such as there are layout properties that must be set in xml if you want to use them. I can't think of what any are off-hand but I can look them up.
For the most part, creating the layouts is much simpler to do in xml but you do have the option of setting Views and layouts in Java if you need to such as creating an unknown number of Buttons depending on some user-defined variable.
When you create your UI in xml then you inflate it in your Java code. This is normally done in onCreate() using
setContentView(R.layout.main);
as you see in your example. But it can also be done with an inflater.
The thing to remember here is to inflate your layout, using either method, before trying to initialize any views in the layout or you will get a NPE when trying to call a method on a View defined before inflating the layout it is contained in.
A correct way
**Examples of inflating views/layouts correctly**
Button mBtn;
public class AndroidDemo extends Activity {
private LinearLayout root;
#Override public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.main);
root = (LinearLayout) findViewById(R.id.root);
btn = (Button) findViewById(R.id.buttonId); // Button is initialized after inflating the layout
}
}
Incorrect way
public class AndroidDemo extends Activity {
private LinearLayout root;
#Override public void onCreate(Bundle state) {
super.onCreate(state);
Button mBtn = (Button) findViewById(R.id.buttonId); // Button is initialized before inflating layout which will return null
setContentView(R.layout.main);
root = (LinearLayout) findViewById(R.id.root);
}
}
I added the above example because I have seen a lot of people make that mistake. So don't do it...you've been warned! :)
Not entirely sure what you're asking, but the two are interchangeable.
Most of the time your UI will be done via xml. In some cases though, the ui is heavily dependent of the data, so you may need to dynamically generate it.
It basically comes down to whichever is easiest for you at the time.
Yes.
But is preferable to use xml, it is more powerful, easier and will separate layout from your code.
Take a look at the docs:
http://developer.android.com/guide/topics/ui/declaring-layout.html
So I have different layouts for this one Activity.
And I have different classes that each open and do their thing with a layout.
I inject these classes in the Activity via #Inject. All this is without problem.
But when I try to use #InjectView on one of the controls that are in the not active layout I get an error.
11-02 19:17:31.086: ERROR/AndroidRuntime(1326): Caused by:
java.lang.NullPointerException: Can't inject null value into class
be.baes.notes.View.EditNoteImpl.saveButton when field is not #Nullable
This would then be the code.
public class EditNoteImpl implements EditNote {
#Inject CancelEditNoteClickListener cancelEditNoteClickListener;
#Inject SaveNoteClickListener saveNoteClickListener;
#Inject Provider<Activity> activity;
#InjectView(R.id.saveButton) Button saveButton;
/* (non-Javadoc)
* #see be.baes.notes.EditNote#activateEditNote()
*/
#Override
public void activateEditNote()
{
activity.get().setContentView(R.layout.editnote);
this.saveButton.setOnClickListener(saveNoteClickListener);
}
}
I can however do this.
public class EditNoteImpl implements EditNote {
#Inject CancelEditNoteClickListener cancelEditNoteClickListener;
#Inject SaveNoteClickListener saveNoteClickListener;
#Inject Provider<Activity> activity;
private Button saveButton;
/* (non-Javadoc)
* #see be.baes.notes.EditNote#activateEditNote()
*/
#Override
public void activateEditNote()
{
activity.get().setContentView(R.layout.editnote);
saveButton = (Button)activity.get().findViewById(R.id.saveButton);
this.saveButton.setOnClickListener(saveNoteClickListener);
}
}
Is there a better way of doing this?
I have just started to use roboguice so someone else with more experience might be able to give a better answer, but this is what I found so far:
In roboguice-1.1.2 (the current stable release), #InjectView items are injected only when setContentView() is called the first time in the Activity. Since your code calls setContentView() dynamically from the auxiliary classes, the injected items there won't be injected correctly.
In roboguice-2.0b2 (the current beta), there is support for multiple setContentView() calls and your code should work. However, injected views are still tied to the context Activity (instead of the declaring class) so every #InjectView will potentially also need to be #Nullable across all auxiliary classes sharing the same Activity.
Since the issue here seems to be rooted in the multiple layout (and hence multiple setContentView() calls) in a single Activity, one alternative way to do what you want is to avoid it as follow:
Instead of having several layouts, use a single layout using the <include/> tag to load all the layouts into a parent FrameLayout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<include layout="#layout/layout1" />
<include layout="#layout/layout2" />
<!-- other layouts... -->
</FrameLayout>
then, instead of calling setContentView(), use a method that will toggle the visible layout on the Activity, something like this:
// instead of: activity.setContentView(R.layout.layout1);
// use: activity.showLayout(R.id.layoutview1);
public void showLayout(int layoutViewId) {
final View view = findViewById(layoutViewId);
final ViewGroup root = (ViewGroup) view.getParent();
for (int i = 0; i < root.getChildCount(); i++) {
final View v = root.getChildAt(i);
v.setVisibility(v == view ? View.VISIBLE : View.GONE);
}
}
The above alternative should work for both the stable and beta roboguice releases. The trade-off here is we are loading several layouts at the same time instead of loading each one at several different times. It seems to work well enough for me (though it might be different for your needs).
One thing I should note is that on the current "Upgrading from RoboGuice 1.1 to 2.0" page, the following is mentioned:
The ability to use #InjectView in Views (although you'll need to call
RoboGuice.injectMembers() yourself, since there's no RoboView base
class and probably never will be).
It seems that this should allow you to implement your auxiliary classes to be derived from View and make #InjectView in them work better (hopefully no need for them to be #Nullable since they are less tied to the Activity). However, looking at the current code, this feature does not seem to be implemented yet (although I could have been looking at the wrong place).
In the app, I'm struggling with I have a custom view.
I cannot declare it in layout XML file, because I'm going to use in from the activity that holds my custom view instance and I need to have access to it (cannot override findViewById...).
Thereof I decided to declare all of the GUI elements into the Activity.
But I simply cannot make a single step forward, since I even cannot instantiate viewGroup...
This is what I'm trying:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewGroup vg = new ViewGroup(this.getApplicationContext());
setContentView(vg);
}
and I get 'Cannot instantiate ViewGroup'...
Can someone give a straight-forward example, of how to declare a viewGroup, that holds views?
The documentation of the class is also not very beginner-friendly... all the examples are focused on describing the layout in a layout XML file...?
Appreciate your efford, giving an example!
[ViewGroup][1] is an abstract class, you cannot instantiate it. It defines a type of classes that will be container to put other views in them. In other words, layouts like LinearLayout of RelativeLayout are ViewGroup. Thus, you could do something like that :
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout vg = new LinearLayout(this);
// set the LayoutParams the way you want.
// and add textviews, imageviews, ... here for instance.
setContentView(vg);
}
For the LayoutParams, I think you should start with LayoutParams.Fill_parent