Compound Controls forwarding State - android

Does anyone know what the appropriate mechanism is when creating a custom compound control to apply state changes from the container down to all the children? It would seem to me there should be a straightforward way to set up a ViewGroup to forward all state changes (pressed, enabled, etc.) to each child.
For example, if I create a custom widget along the lines of:
public class MyWidget extends RelativeLayout {
private TextView mTitleView, mValueView;
private ImageView mImageView;
public ValueButton(Context context) {
this(context, null);
}
public ValueButton(Context context, AttributeSet attrs) {
super(context, attrs);
//View setup, etc.
}
}
In many cases, there are drawable or color state lists attached to the children that I want to toggle when changes apply to the overall widget as a whole. What do I need to add to a widget such as this so that when, for example, I call MyWidget.setEnabled() or when MyWidget is pressed those state changes filter down the hierarchy?
Thanks!

Add android:duplicateParentState="true" to each child (perhaps iterating through the childs using getChildAt(int index))
http://developer.android.com/reference/android/view/View.html#attr_android:duplicateParentState
Edit: you will need to set it up in the xml
Note: in the current implementation, setting this property to true after the view was added to a ViewGroup might have no effect at all. This property should always be used from XML or set to true before adding this view to a ViewGroup.

Not sure if I'm going to be much help here, but it's an interesting problem for me to think about because I think I'm going to want to do something quite similar.
In terms of cascading state information down to child Views, could one possible approach be to only contain that state information in the parent ViewGroup, and in the child Views make use of getParent() to access that information?

Related

Best practice reusing components with MVVM

I was wondering if someone found some clever solution to reuse components (multiple views) when working with MVVM.
By component I mean a set of views that end up being reused in an app.
For instance, an empty state formed of an ImageView and a TextView, and let's also add some sort of ClickListener for the text, for the sake of the example.
Now, what I want to do is to reuse this view in multiple .xml files BUT providing different values for the text, the image, and bind the listener to action in the Fragment's ViewModel.
What I've been doing is create a CustomEmptyState that would extend a LinearLayout or some kind of Layout and add Custom Attributes to it.
So, in the end, I would use my custom view like this:
<com.whatever.customViews.CutomEmptyState
app:image="#drawable/someImage"
app:text="#string/empty_text"
app:onTextClicked="#{viewModel.onEmptyStateClicked()}" />
My question would be, is there a different approach to this? A better one? What I dislike about this is writing the custom attributes with <declare-styleable> and all because then I have to keep track of 3 files:
The .xml layout of the base view
The .java/.kt of the view with the boilerplate code to handle the attributes
The <declare-styleable> with all the attributes
Is there any way to combine 2 and 3?
Say, you have to display some text value which you are sure will be databinded.
Then, if you databind the value, then there is a way, but not an elegant way.
declare a variable in the custom view like: private var status = ""
then write a setter function:
fun setStatus(status: String) {
this.status = status
//refresh your views based on value or set this to the text view
}
and then databind like this:
app:status="#{viewModel.status}"
so that you don't need to declare the stylable anymore
<com.whatever.customViews.CutomEmptyState
app:image="#drawable/someImage"
app:text="#string/empty_text"
app:onTextClicked="#{viewModel::onEmptyStateClicked}" />
public void onEmptyStateClicked(View view){
your code
}

How to know which Layout type is a View in?

I want to extend a View to create a custom View, and automatically force some layout parameters, this way:
public class myView extends View {
public myView(Context context) {
this.setLayoutParameters(LayoutParameters(LayoutParameters.MATCH_PARENT, LayoutParameters.MATCH_PARENT));
}
}
My problem is, how can I set the LayoutParameters if I don't know if the view will be set inside a LinearLayout, a RelativeLayout or somewhere else?
How can I detect which Layout type is the view in?
The layout parameters you're talking about all come from android.view.ViewGroup.LayoutParams which is common to all the ViewGroup classes (LinearLayout, RelativeLayout, etc.)
So you shouldn't need to distinguish between the parent view types; just use the LayoutParams directly.
I just found an easy solution by myself. The question anyway remains valid as soon as there may be a easier and more complete way to do the same thing.
if (this.getParent() instanceof LinearLayout)
this.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));

What's the "Android way" to dynamically create Views with special styles/properties?

As mentioned in topic, I have some Views, e.g. a TableRow with always the same background used as topic, or a special TableRow containing a TextView with some special styles/properties. These Views are set dynamically, so it's problematic to use a XML for this. As I read it's not possible to set styles programmatically too. So what's the best way to solve that?
Possibility 1:
I use and instance derived Views, like this:
public class TopicTableRow extends TableRow {
public TopicTableRow(Context context) {
super(context);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
setBackgroundColor(Color.parseColor("#777777"));
setClickable(false);
}
}
Possibility 2:
I could create a valid xml template with a special layout I never use in the application, containing the needed Views which have already all assigned styles. Afterward I access the needed Views by R.id....
But this method seems to be very dilettante to me.
I don't think that those 2 possibilities are the "real" Android way to do this, so how is this usually done?
If you want to set specific styles for groups of elements, you can use the themes and styles concepts in android.
You can read up on them here: http://developer.android.com/guide/topics/ui/themes.html
It is not possible though to change the style attribute of a view programatically.
Therefore the android way is probably to create the Views you need in XML and use a LayoutInflater to get create an 'java' version of the xml view. This allows you to reuse the component and fill it with apropriate data for as many rows as you would like.
Button view = (Button) LayoutInflater.from(this).inflate(R.layout.textViewFromWeb, null);
I hope this will be of use to you!

Adding Buttons to FingerPaint Android API Sample1

Im kind of new to Android.
I am playing around with the Android FingerPaint API Sample. I have figured out how to add buttons and functions to the Menu, but how do I get buttons on the actual screen?
Is it possible to place buttons over the drawing surface? Or would I need a linear (Vertical) layout on the left, and place buttons in there. Either would be fine.
Help? Thanks.
The Android FingerPaint sample code does not use a layout; it instead just has a subclass of View called MyView, and then the Activity sets its content view to be an instance of MyView.
If you want to have more than one View on the screen, you'll need to use some sort of layout. You could use a LinearLayout if you want the MyView for painting to be above, below, or to the side of the buttons; if you want the buttons to be on top of the MyView, take a look at using a FrameLayout or RelativeLayout.
You can either then define the layout in XML or create it manually in code. The former is more flexible and maintainable, but there will be a few hiccups.
First, create a layout XML showing how you want your components to be laid out. For this example, we'll call it finger_paint.xml. Make sure you have a MyView in there somewhere, something like:
<view class="com.example.android.apis.graphics.FingerPaint$MyView"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
Then, replace the line that looks like
setContentView(new MyView(this));
with
setContentView(R.layout.finger_paint);
Note that because MyView does not (yet) have the proper constructor for being instantiated by the LayoutInflater, this will not work yet, so let's fix that. Add an additional import near the top of the file:
import android.util.AttributeSet;
and then add an AttributeSet parameter to the constructor of MyView:
public MyView(Context c, AttributeSet as) {
super(c, as);
// rest of constructor is same as in the sample
}
You will also have to change MyView to be a static inner class of FingerPaint.
You may find the Building Custom Components document and the NotePad sample useful as you figure this out.
Good luck!
Are you trying to dynamically position your buttons?
If so, you can use setLayoutParams to set the layout properties of the button.

How to highlight ListView item on touch?

I have a simple ListView and I want each of it items to be highlighted on user's touch. I thought this should happen by default but it isn't. Can you advice?
ListView xml:
<ListView
android:id="#+id/list_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
android:divider="#206600"
android:dividerHeight="2dp"
android:smoothScrollbar="true"
android:background="#ffffff"
>
</ListView>
And code of my Adapter:
private class MyAdapter extends ArrayAdapter<Task> {
private LayoutInflater mInflater;
public MyAdapter(Context context, int resource, List<Task> list) {
super(context, resource, list);
mInflater = LayoutInflater.from(context);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
v = mInflater.inflate(R.layout.list_item, null);
}
Task task = taskList.get(position);
/* Setup views from your layout using data in Object here */
return v;
}
You may want to post your actual row layout code, but I suspect the problem will be that you set a background color on your list row. By default, the selectors are drawn behind the list items (which looks nicer, since the highlight color is behind the text). Either don't set a background color on your list items, or set it to draw the selector on top instead with ListView's drawSelectorOnTop XML attribute.
EDIT: If you really must have an opaque background for the default state and don't want to use drawSelectorOnTop, you can also try this: Set a background on your list rows, but use a StateListDrawable to use #android:drawable/list_selector_background for all but the default state (you can define an xml file in your drawables folder for this; see the StateList documentation).
You could also nest a layout inside your outer backgrounded row layout with its background set to #android:drawable/list_selector_background; that way the background would draw on top of your background, but below the content.
ListViews do not retain a visual indication of focus (or selection) while in touch mode. You will only see this when you use the hardware keyboard or controls to navigate your UI.
See the Google Touch Mode Android Blog article for more details.
So, if you are only using touch mode, you will never see focus or selection on ListViews.
I believe this has to do with the "Enabled" attribute of the items in the ListAdapter.
If your Adapter contains the code:
#Override
public boolean areAllItemsEnabled() {
return true;
}
Then each item should be clickable (and therefore should highlight on being touched).
Could you post details (and possibly code) of what kind of Adapter you're using for this list?
I struggled with this for a few days. In the end, I have to create a widget that supports Checkable interface and return that widget/view in my adapter's getiew() function. And the listview needs to be in ListView.CHOICE_MODE_SINGLE (or possibly any other mode specified by android:choiceMode) for it to keep track of the choice made on the UI.
So in essence, all the following needs to be in place for the listview item to stay highlighted:
ListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
ListAdapter.getView() return a view that implements Checkable interface
ListView.OnItemClickListener should call setItemChecked(position, true) to mark the item to be checked (and thus highlight it in the listview)
Hope this can help someone who is also struggling with this.

Categories

Resources