I am trying to make some helper method in my Android project - method to set background with outline. I would like to method accepts different types of object (minimum is different Layouts) and call setBackground() on them. My code:
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
private static <I> void setBackgroundOutlined(Context context,
I item,
String backgroundColor,
String outlineColor, int outlineThickness,
Boolean setStatesPressed) {
GradientDrawable shapeDrawable = new GradientDrawable();
shapeDrawable.setStroke(CF.floatToPixels(context, outlineThickness),
Color.parseColor(outlineColor));
shapeDrawable.setColor(Color.parseColor(backgroundColor));
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_pressed,
android.R.attr.state_enabled},
context.getResources().getDrawable(R.drawable.textview_pressed_color));
states.addState(StateSet.WILD_CARD, shapeDrawable);
if (item instanceof RelativeLayoutWithContextMenuInfo) {
RelativeLayoutWithContextMenuInfo item2= (RelativeLayoutWithContextMenuInfo)item;
if (android.os.Build.VERSION.SDK_INT < 16) {
item2.setBackgroundDrawable(states);
} else {
item2.setBackground(states);
}
} else if (item instanceof LinearLayout) {
LinearLayout item2= (LinearLayout)item;
if (android.os.Build.VERSION.SDK_INT < 16) {
item2.setBackgroundDrawable(states);
} else {
item2.setBackground(states);
}
}
}
I really dislike repeating code in condiditons. Any suggestion to make it more clearly? Thanks, J.
addition to jacobhyphenated answer :
You don't need a generic method as you would only need to pass a parameter of ViewGroup type or its subclasses :
private static void setBackgroundOutlined(Context context,
/* you can pass any layout as item -> */ ViewGroup item,
String backgroundColor,
String outlineColor,
int outlineThickness,
Boolean setStatesPressed) {
// apply you logic on a item based on OS runtime API
}
The setBackgroundDrawable and setBackground methods exist in the ViewGroup class. You can enforce that any item must be or extend ViewGroup in your generic.
private static <I extends ViewGroup> void setBackgroundOutlined(Context context,
I item,
String backgroundColor,
String outlineColor, int outlineThickness,
Boolean setStatesPressed) {
....
if (android.os.Build.VERSION.SDK_INT < 16) {
item.setBackgroundDrawable(states);
} else {
item.setBackground(states);
}
}
But this will only work if you are always using the ViewGroup parent class.
I wouldn't make it as generic as it is now, since you're trying to do something quite specific, still.
You'd better be off swapping I to ViewGroup or View (although this might be too vague, still), or something like that.
It's not very generic to check for the object type in generic code, but using the right superclass for the objects you're trying to support (such that this superclass contains the methods you intent to call on the object) is a better approach.
Related
I need a method to accept two types and do the same thing with them.
I know I could do:
public void myMethod(TextView tv, ReadableMap styles) {
if (styles.hasKey("fontFamily")) {
String font = "fonts/" + styles.getString("fontFamily") + ".ttf";
Typeface tf = Typeface.createFromAsset(context.getAssets(), font);
tv.setTypeface(tf);
}
if (styles.hasKey("fontSize")) {
tv.setTextSize(styles.getInt("fontSize"));
}
if (styles.hasKey("letterSpacing")) {
tv.setLetterSpacing(PixelUtil.toPixelFromDIP(styles.getInt("letterSpacing")));
}
}
public void myMethod(TextPaint tv, ReadableMap styles) {
// copy paste the **exact** same code
}
Both TextView and TextPaint have methods setTypeface(Typeface), setTextSize(float) and setLetterSpacing(float), but the two classes do not have a shared supertype other than Object. (Note that TextPaint inherits the methods from Paint.) These classes both separately declare these methods, with the same names and signatures.
So since I cannot cast one to another, I would like to know how to reduce this amount of duplicated code?
Create an interface class which shares the common method signatures setTypeFace, setTextSize, setLetterSpacing. (Basically the Adapter pattern.)
public interface MyInterface {
Typeface setTypeface(Typeface typeface);
void setTextSize (float textSize);
void setLetterSpacing(float letterSpacing);
}
Redefine the myMethod signature as
public void myMethod (MyInterface myi, ReadableMap styles) {
//...change all your 'tv' references to myi
}
And then invoke with an anonymous class
// myTextView is in scope here...
myMethod(new MyInterface() {
// partial implementation
Typeface setTypeface(Typeface typeface) {
myTextView.setTypeface(typeface);
}, myStyles);
}
// myTextPaint is in scope here
myMethod(new MyInterface() {
// partial implementation
Typeface setTypeface(Typeface typeface) {
myTextPaint.setTypeface(typeface);
}, myStyles);
}
Or create wrappers classes implementing MyInterface for both the TextView and TextPaint.
I do apologise for my comment, I should have checked the super classes and should have used words which make my opinion visible instead of asserting something. To make this right, I tried the following, and it worked. Since I don't have Android SDK available at the moment, I created my own TextView and TextPaint classes and used reflection to call the methods because we already know their signature.
First, change your method signature to public void myMethod(Object tv, ReadableMap styles).
This shows how to call the setTextSize method, but the concept should be the same for all the other methods as well.
tv.getClass()
.getMethod("setTextSize", float.class)
.invoke(tv, styles.getInt("fontSize"));
You can read more about invoking methods via their names using Java Reflection.
how about this
public void myMethod(TextView tv, String styles, TextPaint tp) {
//must tv == null -> tp != null or tv != null -> tp == null
if(tv != null){
tp = tv.getPaint();
}
//doSomthing
}
This is easy to solve, but you need to understand the basic concept of polumorphism.
In order to this be possible you need to have a common ancesstor between these two types
public void myMethod(TextView tv, ReadableMap styles) {this.method(tv,styles);}
public void myMethod(TextPaint tv, ReadableMap styles) {this.method(tv,styles);}
private void method(CommonAncestor tv, ReadableMap styles){/*do your stuff*/}
A common ancestor is a Class or Interface which both of your classes extends or implements
This ancestor must provide BOTH of the common methods you want to call
If you are creating these classes in your project you can simple create the ancestor and make them extend
if they are builtin types you can check for their treeline and find, if two classes share a method with same name and same logic typically they share an ancesstor
if there is no such ancestor, and you want the creation of this "no repeatition" method, you must use reflection... but this is tottally a bad idea, some kind of code smell waiting for crash and i refuse myself to teach it
I have an object with a View member. So i want to pass an ArrayList of this object and i need to pass this View. I know how to implement Parcelable.
The code is something like this :
public class Variable implements Parcelable {
public View mView;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeValue(mView)
}
public static final Parcelable.Creator<Variable> CREATOR
= new Parcelable.Creator<Variable>() {
public Variable createFromParcel(Parcel in) {
return new Variable(in);
}
public Variable[] newArray(int size) {
return new Variable[size];
}
};
private Variable(Parcel in) {
mView= (View) in.readValue(getClass().getClassLoader());
}
So I tried object method for the writeToParcel and Variable(Parcel in) methods but obviously it didn't work.
How I should do this ?
You should only implement parcelable for data objects and never for Views, the idea is to parcel/unparcel the data state to re-instantiate a given View with state.
You should also note that there is a hard set limit on the size of the buffer that a transaction can hold when you parcel your data and send it via Intent. So ideally avoid having large in-memory object being transferred via parcel (one bad example is lets say Bitmaps)
Also, implementing parcelable by hand is pretty tedious and could be error prone. I recommend using Studio's plugin or any good third party plugin for implementing Parcelable.
Hope it helps
I use Sugar ORM for Android Development via Android Studio.
But I think I have a quite similar question.
How can I display one/multiple result queries as String or int?
My entity looks like this:
public class PersonsDatabase extends SugarRecord<PersonsSelection>{
String adultText, childText;
int adultCount, childCount;
public PersonsDatabase()
{
}
public PersonsDatabase(String adultText, String childText, int adultCount, int childCount)
{
this.adultText = adultText;
this.childText = childText;
this.adultCount = adultCount;
this.childCount = childCount;
this.save();
}
}
It saves correctly. But when I want to display like this:
public class PersonsSelection extends Activity {
ListView list;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_persons_selection);
PersonsDatabase personsDatabase = new PersonsDatabase("1 Adult","No Childs",1,0);
List<PersonsDatabase> personsList = PersonsDatabase.findWithQuery(PersonsDatabase.class,"Select adult_Text from PERSONS_DATABASE");
list = (ListView)findViewById(R.id.listView);
list.setAdapter(new ArrayAdapter<PersonsDatabase>(this, android.R.layout.simple_list_item_1, personsList));
}
}
I get something like: PACKAGENAME.PersonsDatabase#4264c038
But I want the values that I wrote in the constructor.
Thanks for help.
From the docs on ArrayAdapter:
However the TextView is referenced, it will be filled with the toString() of each object in the array. You can add lists or arrays of custom objects. Override the toString() method of your objects to determine what text will be displayed for the item in the list.
In short: just override the toString() method in your PersonsDatabase class to return the desired textual respresentation.
Alternatively:
To use something other than TextViews for the array display, for instance, ImageViews, or to have some of data besides toString() results fill the views, override getView(int, View, ViewGroup) to return the type of view you want.
(again from the docs). Plenty of examples out there on how to go about doing that.
Simply override the toString() method.
In that method, return whichever database value you want to retrieve.
In my case, I returned a variable name that I needed (i.e. message) :
#Override
public String toString() {
return message;
}
I have a method in my Android app that can either display from a List of Drawables or Strings (representing the image URLs).
I tried to make two constructors but got the type erasure error. Now I am using a generic argument and check a member for being a String to represent the URL, like so. In the alternate case I assume that it will be a List of Drawables. Something like this:
setImages(List<?> images) {
this.images = images;
if (String.class.isInstance(images.get(0) ) ) {
isImageUrl = true;
}
}
Is there a way to do this better, somehow preserve type safety ?
Why not just use 2 separate methods?
void setImages(List<Drawable> images) {
// do something
}
void setImagesFromStrings(List<String> images) {
// do something else
}
No, there is no real way to do that.
There has been some interesting solution here, but I don't think it applyes for you.
Personnally, I prefer to pass the class as a parameter for the method like
setImages(List<? extends T> images, Class<T> clazz) {
this.images = images;
if ( clazz == String.class ) ) {
isImageUrl = true;
}
}
A better object oriented way to solve you problem would be to have an intermediate interface that provides the method you are looking for, but this will force you to wrap String into something else
interface Displayable {
public boolean isRealImage();
}
class StringImage implements Displayable {
public String name;
public boolean isRealImage() { return false; }
}
class DrawableImage implements Displayable {
public Drawable drawable;
public boolean isRealImage() { return true; }
}
setImages(List<? implements Displayable> images) {
this.images = images;
if (images.get(0).isRealImage()) {
isImageUrl = true;
}
}
Of course this solution, while quite overkill, is meant to allow you whatever you need to do with your classes.
I know this a pretty basic question, and already found another ones like mine, but I honestly don't know what I'm doing wrong.
public class InteractiveArrayAdapter extends ArrayAdapter<Model> {
private final List<Model> list;
private final Activity context;
public int teste;
public InteractiveArrayAdapter(Activity context, List<Model> list) {
super(context, R.layout.rowbuttonlayout, list);
this.context = context;
this.list = list;
}
public int getTest()
{
return teste;
}
static class ViewHolder {
protected TextView text;
protected CheckBox checkbox;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
teste = 2;
....
}
}
and other class:
try{
InteractiveArrayAdapter adapt = new InteractiveArrayAdapter(this,
getAPPS(0));
int test = adapt.getTest();
Toast.makeText(this, Integer.toString(test), Toast.LENGTH_LONG).show();
Log.v("TAG",Integer.toString(test));
}catch(Exception e)
{
Log.v("EXCEPTION",e.toString());
}
EDIT: I was getting null for a stupid mistake, and now I'm getting the primitive and expected 0 as most of you say.
At some point of my app, everytime a checkboxes is clicked that method getView is executed. I want to store that to an array[] of strings progressively (i+1) (i just put int to be easier to understand - realize now it was a mistake), and then when users inputs ok I want to access the whole array. Wondering if it's possible the way I want.
So when I do this
InteractiveArrayAdapter adapt = new InteractiveArrayAdapter(this,
getAPPS(0));
This is meaningless, because I don't need to execute anything again, I just want to retrieve the created array - if possible!
Your code won't even compile. return this.teste; should be return this.test;.
Well, this isn't a direct copy/paste, since this obviously wouldn't compile. Whenever you're dealing with an actual error or issue, it's really best to paste the actual code. We're all programmers, so we can read it.
But based on the structure you've shown above, either the typo you've put in the line return this.teste (should be return this.test) is in your code, or you didn't initialize the instance variable test in your constructor.
Without showing us the actual code you're writing, it's impossible to say (especially the section that initializes the test variable, and the part that returns its value are missing - we're not mind readers, I'm afraid).
So, those are two potential candidates. On another note, however, if you mark the test variable as public, then you don't need to have getter/setter methods for them, since any class can access them without going through a method call. That's what public does.
But that is what should happen according to your code. You don't call B method to update teste variable.