Code equivalent of android:cropToPadding - android

In my android app, I create thumbnails in xml this way:
<ImageView android:id="#+id/my_thumbnail
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#color/white"
android:padding="2dp"
android:scaleType="fitXY"
android:cropToPadding="true" />
This creates a white border around the thumbnail - and looks quite nice.
Now, in another place in the code I need to create the thumbnails in the code, not XML (this is an Adapter class for GridView - to create a grid of thumbnails). I can set all parameters as required, however I cannot find a way to set the cropToPadding in the code. As a result, the thumbnail are drawn over the padding and it looks really ugly.
How do I set this cropToPadding thing in the code?
BTW, the app must work on Android 1.6.
Edit
Following a suggestion from userSeven7s, I add this style to my XML containing other styles:
<style name="GridImage">
<item name="android:background">#color/white</item>
<item name="android:padding">2dp</item>
<item name="android:cropToPadding">true</item>
<item name="android:typeface">monospace</item>
</style>
(Note that the file contains <resources> root element and contains a couple of other <style> elements. Yet all of the other ones are only referenced form layout xml files.)
I then added this code inside my grid view adapter:
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
XmlPullParser parser = mContext.getResources().getXml(R.style.GridImage);
AttributeSet attributes = Xml.asAttributeSet(parser);
imageView = new ImageView(mContext, attributes);
// ... some more initialisation
} else {
imageView = (ImageView) convertView;
}
// ... code to create the bitmap and set it into the ImageView goes here
}
This compiles fine (i.e. R.style.GridImage exists). However when I run this code, the app crashes with Resources.NotFoundException on getXml(R.style.GridImage).
Any suggestions?

If you try the solution with AttributeSet and attributes.getAttributeCount() always returns 0, then just about the only solution left is this hack using reflection:
Field field = ImageView.class.getDeclaredField("mCropToPadding");
field.setAccessible(true);
field.set(imageView, true);
Using this you probably can't be sure it will work in other android firmware versions.
Anyway the missing setter is probably just forgotten, so that justifies the hack for me.

You can get the thumbnails from the ImageView itself. Call getDrawingCache() on each imageview to get the bitmap used for drawing.
new Update :
Create a xml myimage_attrs.xml in xml folder and add all the imageview attributes you need to it.
<?xml version=”1.0″ encoding=”utf-8″?>
<item name=”android:layout_height”>10dp</item>
<item name=”android:layout_width”>10dp</item>
<item name="android:background">#color/white</item>
<item name="android:padding">2dp</item>
<item name="android:cropToPadding">true</item>
<item name="android:typeface">monospace</item>
....
Then create a AttributeSet out of the xml and pass it to the ImageView constructor.
XmlPullParser parser = resources.getXml(R.xml.myimage_attrs);
AttributeSet attributes = Xml.asAttributeSet(parser);
ImageView imgv = new ImageView(context, attributes);

For the benefit of others, here's what finally did work (thanks to userSeven7s for pushing me in the right direction).
I created grid_image.xml file with the following content:
<?xml version="1.0" encoding="utf-8"?>
<style>
<item name="android:layout_height">50dp</item>
<item name="android:layout_width">50dp</item>
<item name="android:background">#color/white</item>
<item name="android:padding">2dp</item>
<item name="android:cropToPadding">true</item>
<item name="android:scaleType">fitXY</item>
</style>
Then in my Adapter for the grid view, I put the following code:
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
XmlPullParser parser = mContext.getResources().getXml(R.xml.grid_image);
AttributeSet attributes = null;
int state = XmlPullParser.END_DOCUMENT;
do {
try {
state = parser.next();
if (state == XmlPullParser.START_TAG) {
if (parser.getName().equals("style")) {
attributes = Xml.asAttributeSet(parser);
break;
}
}
} catch (Exception ignore) {
//ignore it - can't do much anyway
} while(state != XmlPullParser.END_DOCUMENT);
if(attributes == null)
imageView = new ImageView(mContext);
else
imageView = new ImageView(mContext, attributes);
//the rest of initialisation goes here
} else {
imageView = (ImageView) convertView;
}
//set image into the imageview here
return imageView;
}

Related

How to control NavigationView margin between menu items

I have a navigation drawer with many items so the user needs to scroll up and down in order to see all the items.
I would like to reduce the margins between individual menu items, so that all items fit within a standard screen with no need to scroll.
Is there a way to control the margins in between the menu items?
Answer here helped me to reduce space at least between groups. I used the following dimen
<dimen tools:override="true" name="design_navigation_separator_vertical_padding">1dp</dimen>
We can create a drawable and set it as the NavigationView's itemBackground attribute. I will explain below:
If we walk through the NavigationMenuAdapter, we will see there are four types of items:
private static final int VIEW_TYPE_NORMAL = 0;
private static final int VIEW_TYPE_SUBHEADER = 1;
private static final int VIEW_TYPE_SEPARATOR = 2;
private static final int VIEW_TYPE_HEADER = 3;
What we want to work with is VIEW_TYPE_NORMAL. The attributes exposed to developers can be found in the below code:
case VIEW_TYPE_NORMAL:
{
NavigationMenuItemView itemView = (NavigationMenuItemView) holder.itemView;
itemView.setIconTintList(iconTintList);
if (textAppearanceSet) {
itemView.setTextAppearance(textAppearance);
}
if (textColor != null) {
itemView.setTextColor(textColor);
}
ViewCompat.setBackground(
itemView,
itemBackground != null ? itemBackground.getConstantState().newDrawable() : null);
NavigationMenuTextItem item = (NavigationMenuTextItem) items.get(position);
itemView.setNeedsEmptyIcon(item.needsEmptyIcon);
itemView.setHorizontalPadding(itemHorizontalPadding);
itemView.setIconPadding(itemIconPadding);
if (hasCustomItemIconSize) {
itemView.setIconSize(itemIconSize);
}
itemView.setMaxLines(itemMaxLines);
itemView.initialize(item.getMenuItem(), 0);
break;
}
Unfortunately,there is no interface for us to add margins between the NavigationMenuItemView. However, it allows us to set a background. So we can set a customer drawable to the NavigationView with a specific height. We only set a height in that drawable, like:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle">
<size android:height="60dp"/>
</shape>
Then apply this drawable to the NavigationView in the layout.xml, like:
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:itemBackground="#drawable/bk_menu_item"/>
I understand it is not a perfect solution, but it seems the only solution working in my case.
Paste this in styles.xml
<style name="NavigationTheme" parent="AppTheme">
<item name="android:textSize">16sp</item>
<item name="android:layout_marginBottom">02dp</item>
</style>
In navigation drawer put this in each item:
android:theme="#style/NavigationTheme"
add below code in dimens.xml
<dimen tools:override="true" name="design_navigation_icon_padding">16dp</dimen>

TextView DrawableCompound color change when in a ListView

Am having a weird behaviour when setting a Drawable color using setColorFilter when the Drawable is a TextView DrawableCompound and the TextView is the view use to render elements on a ListView.
The color doesn't apply to the Drawable when i load the elements of the list for the first time
but when i scroll (down then up) the list (forcing the call to Adapter.getView()) some of them get updated with the expected color.
What's even weird is that when i touch the list element that has the expected color it goes back to it's default color.
Here is my code :
protected class ViewHolderMesure {
Context context;
#InjectView(R.id.stationBandeau_mesureTextView) TextView measureTextViewWithIndicator;
public ViewHolderMesure(Context ctx, View view) {
ButterKnife.inject(this, view);
context = ctx;
}
public void populateViews(Measure mesure) {
measureTextViewWithIndicator.setText(mesure.toLabelAndValue());
if (mesure.getRawLevel() >= 0) {
measureTextViewWithIndicator.setTextColor(ContextCompat.getColor(context, R.color.gris_fonce));
}
else {
measureTextViewWithIndicator.setTextColor(ContextCompat.getColor(context, R.color.gris_clair));
}
if (mesure.hasSeuils()) {
Drawable indicator = measureTextViewWithIndicator.getCompoundDrawables()[0].mutate();
String hexaColor = mesure.getColor();// for instance"#45DA75" this hexa ref comes from a my data source which i get from a webservice call. I don't know how many colors they might be
indicator.setColorFilter(Color.parseColor(hexaColor), PorterDuff.Mode.SRC_ATOP);
indicator.invalidateSelf(); //FIXME: setColorFilter doesn't really set the Drawable color correctly
}
}
}
protected class MesureBandeauAdapter extends ArrayAdapter<Measure> {
public MesureBandeauAdapter(Context context, int resource) {
super(context, resource);
}
#SuppressLint("InflateParams")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Measure mesure = getItem(position);
ViewHolderMesure viewHolderMesure;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.bandeau_mesure_list_item, null);
viewHolderMesure = new ViewHolderMesure(getContext(), convertView);
convertView.setTag(viewHolderMesure);
}
else {
viewHolderMesure = (ViewHolderMesure) convertView.getTag();
}
viewHolderMesure.populateViews(mesure);
return convertView;
}
}
The xml TewtView and Drawable i use :
<TextView
android:id="#+id/stationBandeau_mesureTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:padding="#dimen/stationBandeau_innerPadding"
android:text="#string/station_bandeau_libelle_hint"
android:textColor="#color/gris_fonce"
android:textSize="#dimen/stationBandeau_secondaryTextSize"
android:width="#dimen/stationBandeau_measurementWidth"
android:drawableLeft="#drawable/ic_alert_green_indicator"
android:drawablePadding="#dimen/stationBandeau_statusIndicatorSpacement"
tools:ignore="RtlHardcoded" />
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#color/blanc"/>
<size android:width="#dimen/stationBandeau_containerPadding"
android:height="#dimen/stationBandeau_containerPadding" />
</shape>
I've try to use a mutable Drawable as sugested by http://www.curious-creature.com/2009/05/02/drawable-mutations/, try to invalidate the Drawable after i set the color as sugested in https://stackoverflow.com/a/10749079/665823, etc.. with little success.
I hope someone can help me with this.
Edit
This isn't a duplicate of Change drawable color programmatically, changing alarme.setColorFilter(Color.parseColor("#45DA75"), PorterDuff.Mode.SRC_ATOP); for alarme.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#45DA75"), PorterDuff.Mode.MULTIPLY)); changes absolutely nothing to my problem. And besides from Android source code :
/**
* ...
* Convenience for {#link #setColorFilter(ColorFilter)} which constructs a
* {#link PorterDuffColorFilter}.
* ...
*/
public void setColorFilter(#ColorInt int color, #NonNull PorterDuff.Mode mode) {
setColorFilter(new PorterDuffColorFilter(color, mode));
}
Also getting the drawable from context Drawable alarme = ContextCompat.getDrawable(context, R.drawable.ic_alert_green_indicator).mutate(); and setting the drawable compound valeur.setCompoundDrawablesWithIntrinsicBounds(alarme, null, null, null); instead of getting it from the TextView Drawable alarme = valeur.getCompoundDrawables()[0].mutate() it's self didn't help either.

Inflate into "this"?

I've been creating apps without much XML, creating views programmatically. I'd like to switch to XML. So I wrote an XML file for a RelativeLayout, and I need to inflate it into an existing class (a subclass of RelativeLayout, of course) that has all the implementation logic.
How do I inflate into "this" in the constructor?
By the way, what's really the advantage of XML? When I create views in the code, I scale fonts and images and also move views around depending on the screen's size, orientation, aspect ratio, etc. With XML approach, I'd have to create a separate XML for all possible configurations...
Constructor code:
public OrderEditControl()
{
super(LmcActivity.W.getApplicationContext());
Resources res = LmcActivity.W.getResources();
setBackgroundColor(Color.TRANSPARENT);
headers = res.getStringArray(R.array.item_list_columns);
widths = new int[headers.length];
createLabels();
createButtons();
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_TOP);
lp.addRule(RIGHT_OF, labels[LabelType.CUSTOMER.ordinal()].getId());
lp.addRule(LEFT_OF, buttons[ButtonType.FIND_CUSTOMER.ordinal()].getId());
customerView = new TextView(LmcActivity.W.getApplicationContext());
customerView.setTextColor(Color.BLACK);
customerView.setId(400);
customerView.setTypeface(Typeface.DEFAULT_BOLD);
customerView.setGravity(Gravity.CENTER_VERTICAL);
addView(customerView, lp);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_TOP, labels[LabelType.SHIP_TYPE.ordinal()].getId());
lp.addRule(ALIGN_BOTTOM, labels[LabelType.SHIP_TYPE.ordinal()].getId());
lp.addRule(RIGHT_OF, labels[LabelType.SHIP_TYPE.ordinal()].getId());
shipSpinner = new Spinner(LmcActivity.W);
shipSpinner.setId(401);
shipSpinner.setAdapter(shipAdapter);
shipSpinner.setOnItemSelectedListener(this);
addView(shipSpinner, lp);
deliveryView = new EditText(LmcActivity.W.getApplicationContext());
deliveryView.setGravity(Gravity.CENTER_VERTICAL);
deliveryView.setSingleLine();
deliveryView.setId(402);
addView(deliveryView);
lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(RIGHT_OF, labels[LabelType.COMMENTS.ordinal()].getId());
lp.addRule(LEFT_OF, buttons[ButtonType.ITEMS.ordinal()].getId());
lp.addRule(ALIGN_TOP, labels[LabelType.COMMENTS.ordinal()].getId());
commentView = new EditText(LmcActivity.W.getApplicationContext());
commentView.setGravity(Gravity.TOP);
commentView.setId(403);
addView(commentView, lp);
lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
lp.addRule(BELOW, commentView.getId());
itemList = new ListView(LmcActivity.W.getApplicationContext());
itemList.addHeaderView(createRow(null, null), null, false);
itemList.setOnItemClickListener(this);
itemList.setAdapter(itemAdapter);
itemList.setCacheColorHint(0);
itemList.setBackgroundColor(Color.TRANSPARENT);
itemList.setId(404);
addView(itemList, lp);
lays[0] = new LayParm(false);
lays[1] = new LayParm(true);
}
/** create the view's buttons */
private void createButtons()
{
for (int i = 0; i < N_BUT; ++i)
{
Button but = i == ButtonType.ITEMS.ordinal() ?
new TextGlassButton(2.4f, LmcActivity.W.getResources().getString(R.string.items), Color.WHITE) :
new EffGlassButton(1.2f, butEffects[i]);
but.setId(BUT_ID + i);
but.setOnClickListener(this);
buttons[i] = but;
if (i == ButtonType.DATE.ordinal())
addView(but);
else
{
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (i < 2)
lp.addRule(ALIGN_PARENT_TOP);
else
lp.addRule(BELOW, BUT_ID + i - 2);
if (i % 2 == 0)
lp.addRule(ALIGN_PARENT_RIGHT);
else
lp.addRule(LEFT_OF, BUT_ID + i - 1);
addView(but, lp);
}
}
}
/** create text labels */
private void createLabels()
{
Paint paint = AFDraw.W.textPaint;
paint.setTextSize(Universe.TEXT_SIZE);
paint.setTypeface(LmcActivity.W.defaultTypeface);
String[] titles = LmcActivity.W.getResources().getStringArray(R.array.order_labels);
for (int i = 0; i < titles.length; ++i)
{
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_LEFT);
if (i == 0)
lp.addRule(ALIGN_PARENT_TOP);
else
lp.addRule(BELOW, LABEL_ID + i - 1);
TextView tv = new TextView(LmcActivity.W.getApplicationContext());
tv.setText(titles[i]);
tv.setTextColor(Color.BLACK);
tv.setId(LABEL_ID + i);
tv.setTypeface(LmcActivity.W.defaultTypeface);
tv.setGravity(Gravity.CENTER_VERTICAL);
labels[i] = tv;
addView(tv, lp);
labelWidth = Math.max(labelWidth, paint.measureText(titles[i]));
}
labelWidth += Universe.TEXT_SIZE * 0.5f;
dateWidth = paint.measureText("00/00/00") + Universe.TEXT_SIZE * 1.5f;
}
#scriptocalypse is generally right, but subclassing some layouts and inflating custom layout to this class helps to separate different abstractions. There are so many bad tutorials, in which everything is done in the Activity. I see that the world's new comming programmers will code only crap looking applications.
With custom layout you can do in Activity only such a thing:
medicineView.putMedicine(medicineList);
instead of all crappy adapter creations and looking for views...
Firstly you should create some view for your custom View:
<RelativeLayout ...>
<!-- You put all your views here -->
</RelativeLayout>
Secondly if you are sattisfied with your view, you should change the root to merge tag:
<merge ...>
<!-- You put all your views here -->
</merge>
This is very important. We begin design with RelativeLayout tags in order to IDE know how to draw layouts, and how to do completions. But if we leave it as it is, we will end up in two nested RelativeLayouts it will be something like that in the end:
<RelativeLayout ...> <!-- That is your class -->
<RelativeLayout ...> <!-- This is inflated from layout -->
<!-- You put all your views here -->
</RelativeLayout>
</RelativeLayout>
If you change your layout to "merge" then it will look like this:
<RelativeLayout ...> <!-- That is your class -->
<merge...> <!-- This is inflated from layout -->
<!-- You put all your views here -->
</merge>
</RelativeLayout>
and will be merged to its root:
<RelativeLayout ...> <!-- That is your class, merged with layout -->
<!-- You put all your views here -->
</RelativeLayout>
At the end you must subclass demanded View or ViewGroup:
public class CustomView extends RelativeLayout {
public CustomView(Context context) {
super(context);
initialize();
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.id.your_layout, this, true);
// find your views, set handlers etc.
}
}
Usage
Just like #scriptocalypse already said. In another layout you use this like that:
<SomeLayout>
<com.foo.CustomView>
</SomeLayout>
First, to answer your main question:
you would not want to inflate an XML RelativeLayout into your RelativeLayout class. You'd extend RelativeLayout and then declare an instance of your RelativeLayout in an XML file, like so:
// com.foo.MyRelativeLayout.java
public class MyRelativeLayout extends RelativeLayout{
/**
* Implement MyRelativeLayout
*/
}
and...
// layout_example.xml
<?xml version="1.0" encoding="utf-8"?>
<com.foo.MyRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Put More Views in here... -->
<TextView
android:id="#+id/customer_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/customer_name_placeholder" />
<!-- and on... -->
</com.foo.MyRelativeLayout>
But more to the point, if you're using the XML to lay out your file, you don't need any of those instantiations or .addRule() method invocations inside your MyRelativeLayout file because you've done it declaratively in XML instead.
To answer your second question of "Why do you want to use XML anyway?"
There are many reasons. Maybe these apply to you, and maybe they don't, but they're ones that I can think of fairly easily that have been relevant in my work.
You don't actually have to create a new layout file for every separate screen size or use case. For the most part, a single layout file will suffice for most screens. You might find that you will have size/resolution/orientation specific dimens.xml or style.xml files, but unless you want a dramatically different arrangement for your different possibilities then the layouts themselves don't repeat themselves too often.
You can use a visual editor. This is important if you're working in teams, and your teammates don't like to or want to use only Java to lay out their screens. While I and others gladly create View and Layout subclasses to fit our needs, I know of literally nobody who prefers to use Java as their primary layout language. Finding people who will work with you (or a job where everyone else uses the XML tools) could be challenging.
If you're creating tools for other people to use (like the above-mentioned folks who prefer XML) you can actually give them custom attributes to work with, that make positioning and layout more powerful. These attributes could be hard-coded in the XML, or they could be references to any of the other Android resources (drawable/string/color/integer/boolean/etc...). As a contrived example, but one based on your code, you could give your users the ability to specify a number of buttons to create rather than rely on the N_BUT variable. You could give it a default value, but offer users a way to change it in XML.
Here is an example:
// somelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.foo.MyRelativeLayout
xmlns:param="http://schemas.android.com/apk/res-auto"
style="#style/MyRelativeLayoutStyle"
param:numberOfButtons="3">
</com.foo.MyRelativeLayout>
and in a different file...
//attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyRelativeLayout">
<attr name="numberOfButtons" format="reference|integer" />
</declare-styleable>
</resources>
and in your MyRelativeLayout, you access those attributes from the AttributeSet in its constructor (the one called by Android when it uses XML to create a layout).
Using the style="#style/foo" syntax can allow you to create "classes" of styles that can apply to all kinds of views without actually making a View subclass. Let's say you know that you always want to have a set of parameters that hold true for all your Button elements but don't want to subclass Button.
For example:
// styles.xml
<style name="BaseButton">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:background">#drawable/bg_common_button</item>
<item name="android:textColor">#color/white</item>
<item name="android:textSize">#dimens/base_button_text_size</item>
<!-- ^^ that dimen value could vary from screen size to screen size, but the style likely won't -->
</style>
// button_layout.xml
<Button
android:id="#+id/styled_button"
style="#style/BaseButton" /> <!-- and you're done -->
// some_other_layout.xml
<LinearLayout
style="#style/BaseLinearLayout">
<Button style="#style/BaseButton" android:text="Button1" />
<Button style="#style/BaseButton" android:text="Button2" />
<Button style="#style/BaseButton" android:text="Button3" />
</LinearLayout>
If you would like to instantiate that button using code, then you can use the LayoutInflater to inflate that specific button's layout and use that wherever you want. In fact, you can create all manner of components in XML and then inflate them at runtime.
LayoutInflater inflater = LayoutInflater.from(YourActivity.this);
Button theInflatedButton = inflater.inflate(R.layout.button_layout.xml, null);
Of course, the canonical example is ListViews and the items that you wish to populate them. You'd create a listview item layout xml and then inflate that whenever your Adapter is in need of a new convertView instance.

ExpandableListView indicator is not aligned correctly

I created a ExpandableListView programmatically and I'm using TextView for the groupitems. I now get a problem with the groupIndicator which propably doesn't occur when the ExpandableListView is declared within a xml : The indicator is drawn directly to top of the groupItem ("no paddingTop"). I'm using padding on my TextViews so it looks like the indicator is not aligned to the TextView in height. How can I align the indicator correctly?
Thx
EDIT:
within my custom BaseExpandableListAdapter:
#Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
Context c = context.get();
if(c==null)
return null;
if(convertView==null){
convertView = new TextView(c);
TextView t = (TextView) convertView;
t.setText(groupData.get(groupPosition));
t.setPadding(left, groupPadding, left, groupPadding); // left '=' android.R.attr.expandableListPreferredItemPaddingLeft
t.setTextSize(TypedValue.COMPLEX_UNIT_PX, groupTextSize);
t.setTextColor(Color.WHITE);
return t;
}
TextView t = (TextView) convertView;
t.setText(groupData.get(groupPosition));
return t;
}
This is a screenshot of one unexpanded groupItem :
As you can see the indicator drawable touches the top-edge of the groupItem.
EDIT2:
I set a custom Drawable as indicator in the hope that it will fix my problem. At first my drawable was stretched to fit groupItem's height. I'm now using 9patchs which prevent the indicator from getting stretched, but this cause the indicator to be aligned to the bottom (instead of to the top as before). Well, still the same problem just reversed. Any ideas how I can fix the problem using my custom drawable? Maybe there is a way to add padding to the indicator (because I know the drawable height and the groupItem's height)?
//indicator_selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_expanded="true" android:drawable="#drawable/arrow_right" />
<item android:drawable="#drawable/arrow_down" />
</selector>
//in MainActivity.onCreate()
expView = new ExpandableListView(context);
indicator = context.getResources().getDrawable(R.drawable.indicator_selector);
expView.setGroupIndicator(indicator);
expView.setIndicatorBounds(expView.getPaddingLeft(), expView.getPaddingLeft()+indicator.getIntrinsicWidth());
According to this question and the answer of coderat I figured out how to solve my problem using 9patches [ ExpandableListView - group indication is stretch to fit the text size ] :

Can't understand Android custom drawable state

I'm new in Android development and I'm writing a small app to understand how it works. I've got all working, but at the moment I can't get a point about custom drawable states... let me explain with some sample code.
Here is my attrs.xml, in which I declare a attribute with name "oddMonth", which is boolean:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DayView">
<attr name="oddMonth" format="boolean"/>
</declare-styleable>
</resources>
Then I have a custom View:
<?xml version="1.0" encoding="utf-8"?>
<com.example.calendar.DayView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="#drawable/dayview_state" >
<TextView android:id="#+id/day_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:paddingRight="3dp" />
</com.example.calendar.DayView>
So I put the line "android:background="#drawable/dayview_state"", which refers to file dayview_state.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:easycalendar="http://schemas.android.com/apk/res/com.example.calendar">
<item easycalendar:oddMonth ="true" android:drawable="#drawable/customborder_odd" />
<item easycalendar:oddMonth ="false" android:drawable="#drawable/customborder_even"/>
</selector>
So far... for what I can understand.... I have a attribute defined in attrs.xml. This attribute represents the state for my custom view. According to the boolean value of this attribute my app will load one of two different xml (that are not important here), each of one defines a different drawable. So the final step is to build my custom class! Follows a extract from the class:
public class DayView extends RelativeLayout {
private static final int[] STATE_ODD_MONTH = { R.attr.oddMonth };
private boolean mOddmonth = true;
public DayView(Context mContext, AttributeSet attrs) {
super(mContext, attrs);
}
#Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mOddmonth) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
mergeDrawableStates(drawableState, STATE_ODD_MONTH);
return drawableState;
} else {
return super.onCreateDrawableState(extraSpace);
}
}
public boolean isOddMonth() {
return mOddmonth;
}
public void setOddMonth(boolean oddMonth) {
if (mOddmonth != oddMonth) {
mOddmonth = oddMonth;
refreshDrawableState();
}
}
}
Ok... so I have here a private variable mOddMonth, whith getter and setter. The constructor which is used to inflate this view elsewhere. Another private variable:
private static final int[] STATE_ODD_MONTH = { R.attr.oddMonth };
which is a array made up of only one int value, that is a reference to the attribute oddMonth defined in attrs.xml. And the inherited method:
#Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mOddmonth) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
mergeDrawableStates(drawableState, STATE_ODD_MONTH);
return drawableState;
} else {
return super.onCreateDrawableState(extraSpace);
}
}
which I can't really "deeply" understand... well, it seems to me that I add a state if the local variable mOddMonth is true, otherwise not. So... my code works only if I replace my dayview_state.xml with the following:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:easycalendar="http://schemas.android.com/apk/res/com.example.calendar">
<item easycalendar:oddMonth ="true" android:drawable="#drawable/customborder_odd" />
<item android:drawable="#drawable/customborder_even"/>
</selector>
In this way the first layout is loaded if THERE IS the state, otherwise will be loaded the second one. But WHAT ABOUT THE VALUE of the state? Nowhere in my code I set the value for this variable/attribute.... where I'm wrong?
I would recommend you reword your question b/c it wasn't clear what you were asking until I read your comment to #kcoppock's answer, which is -
"what i want to do (or I think I should do) is to set this value
somewhere in code according to the actual status of my custom view,
and then force it to render again.... Or I shouldn't?"
At any point, you can query the view to get it drawable state using View.getDrawableState.
If based on this, you want to re-render your drawable, then you have several options.
First of all you can call Drawable.invalidateSelf. But you rarely need to do that because usually your drawable is set as a view's background drawable which is automatically drawn for you in the draw method (not onDraw, which is what you draw). So all you need to do in that case is to invalidate the view (view.invalidate), it will automatically redraw your background drawable (hence picking up your drawable state change).
If you are using your drawable not as a background but for your main drawing then you draw your drawables in onDraw. A simple myDrawable.draw(canvas) should be enough. But remember to vall view.invalidate to trigger the onDraw method.
You're correct; you'll need to assign that value in your constructor with the AttributeSet variable:
TypedArray values = context.obtainStyledAttributes(attrs, STATE_ODD_MONTH);
boolean isOddMonth = values.getBoolean(R.attr.oddMonth, false);
mOddmonth = isOddMonth;
values.recycle();
I believe this should do the trick. I usually use a declare-styleable tag in attrs.xml instead of hardcoding an int[], but I believe it should work identically.

Categories

Resources