Passing values to custom view in android - android

Cheers,
I have an app that receives user input (2 numbers, width and height) and in theory depending on that input I have a custom view that should draw a grid (width and height).
Note:
These 2 values should be received before view attempts to draw itself.
These 2 values aren't constant and therefore I don't think XML approach can help.
I was told that adding another parameter to the View constructor is evil.
Do not confuse my 2 values with canvas.getWidth or etc.. these are values needed simply to draw something, nothing else.
My View is also a ViewGroup.
Main issue arises with Views declared in XML files.
I have temporarily solved this issue by making an SchemeContext class which contains those 2 static values and I simply set them in onCreate (before onCreateView) then use them in custom View onDraw when needed (SchemeContext.width). This is not really what people would call OOP I'm forcing global variables upon java and those are set on time because of the fragment lifecycle.
I've seen this answer How to pass variables to custom View before onDraw() is called?.
But it's more of a workaround than a solution (and probably not the fastest one). There has to be a sensible solution I don't think 3D games on android resort to these workarounds (SurfaceView with OpenGL is still a View right? :d).
If there is an obvious solution and this is an obvious double I'll remove the question.

I haven't tried this, but I think it would be possible to do this fairly cleanly by overriding the LayoutInflater.Factory. That way, you can intercept the creation of the views that need additional parameters passed to their constructors, and let the rest of them fall through to default inflation.
For example, in your activity, before you inflate the view hierarchy:
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
MyInflaterFactory factory = new MyInflaterFactory();
// Pass information needed for custom view inflation to factory.
factory.setCustomValue(42);
inflater.setFactory(factory);
For your implementation of the factory:
class MyInflaterFactory implements LayoutInflater.Factory {
public void setCustomValue(int val) {
mCustomVal = val;
}
#Override
public View onCreateView (String name, Context context, AttributeSet attrs) {
if (name.equals("com.package.ViewWithCustomCreation")) {
return new ViewWithCustomCreation(context, attrs, mCustomVal);
}
return null;
}
private int mCustomVal;
}

I was told that adding another parameter to the View constructor is evil.
Nonsense.
There are three (and in the newest APIs, four) different View constructors, each used in a different situation. (See this thread.) If you wanted to be able to declare your view in XML, for example, then you'd have to provide a constructor with exactly the right parameters, and have it call the corresponding superclass constructor. But there's nothing wrong with defining your own constructor (or even several of them) that call the superclass constructor intended for creating views programmatically.
The overriding principle is that every object must be valid when its constructor returns. So unless you can provide reasonable default values in your constructor, you have little choice but to accept the object's properties as constructor parameters.

Related

custom view with extra string parameters in constructor

How can I passs multiple strings into custom views constructor in android
public DrawSomethingView(Context context, String originalBgPath, String pattern) {}
and why it forces to do it like that ?
public DrawSomethingView(Context context, AttributeSet S){
You need the standard constructors so the framework knows how to inflate them, e.g. from an XML layout. If you put a DrawSomethingView in your layout, how would it know what to pass for originalBgPath etc? You also need a Context and AttributeSet at a minimum to call one of the View superclass's constructors. You can read more about it here
That link also tells you how to create your own attributes, that you can add to the XML for your custom view, and pull out the data in the class itself. So you could add your strings as part of the XML definition. If you want to set them programmatically instead, you'll need to add some properties you can set (which you could also set when you read those attributes).
But there's no way to force them to be set, since you can't create your own version of the constructor that requires them (and the framework wouldn't have values to provide anyway). So you'll need to handle the possibility they're missing (e.g. nullable properties, default values), and maybe write a builder function that you can call from your code, that does require those values and creates and sets up the custom view before passing it back to you.

How to find a View in a BridgeContext?

TL;DR: Is there anything in com.android.layoutlib.bridge.android.BridgeContext that can substitute for Activity#findViewById(...)? I've looked at the source, but I can't find anything.
When running on a real device, an attached view's #getContext() returns the Activity. The view can cast it and call #findViewById(...) to obtain a reference to some other view.
But when running in a WYSIWYG editor, #getContext() returns an instance of a different class. I'm getting com.android.layoutlib.bridge.android.BridgeContext. This class isn't part of the public API, so I'm planning to access it via reflection and degrade gracefully if the implementation changes.
If you're wondering why my view wants a reference to another view... I've created a view that appears to have a hole in it. It works by delegating its drawing to another view. If the view with the hole is placed on top of other views, then it appears to punch a hole through any views beneath it, all the way down to the view it's using for drawing. It works perfectly on a real device, but it would be nice to have it also work in the WYSIWYG editor.
It's bad to assume that View.getContext(), or any other platform method that returns Context, can be cast directly to more concrete classes, like Activity. There exist classes like ContextThemeWrapper which can easily destroy your assumption.
I would recommend restructuring what you are doing so that you have a parent layout that can act as an intermediary for the hole-y View and what's below it.
Or you could have a setter which would provide the View for you.
A last option is to call View.getParent() a bunch of times to get the root View and call findViewById() on that:
ViewParent parent;
while(getParent() != null) {
parent = getParent();
}
View root = (View) parent;
root.findViewById(R.id.my_view);
BTW, BridgeContext is used in the WYSIWYG in place of Activity because it only mocks the Android View/Layout/Rendering system, it doesn't emulate it completely. This can be seen in other ways like how it renders shadows or shape drawable rounded corners.
I awarded the bounty to dandc87 because his answer led me to the solution. However, the code snippet in his answer crashes with a ClassCastException because the root ViewParent is not a View. The mods keep rejecting my edits, so here's the complete and correct solution:
private View findPeerById(int resId) {
View root = this;
while(root.getParent() instanceof View) {
root = (View) root.getParent();
}
return root.findViewById(resId);
}

How to extend a complex Android view like the GridView?

Background:
I'm looking for a way to extend the GridView I need implement a col- and row-span like in HTML. I have hunderds of elements so I cannot use e.g. a TableLayout. However that would be too localized. So far I reduce the question to how to extend the GridView?
What I have tried and where I have failed:
My first approach was to look which methods I need to replace and I found some private methods which cannot be overriden. So I tried to copy the hole source code from the GridView to a new class called CustomGrid. That was a terrible failure since I cannot access the com.android.internal.R class.
Then I dropped that idea and looked if I can normal extend the class and replace all the private methods with custom copies. I took a pen and build a huge tree where all methods are used.
After I found all references I tried to extend the class normal and I added the first method:
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
// ...
Here is my next problem that member variables mBottom and mTop are unknown. I digged a little through the sources and found them finally in the View class, but unfortunately they are hidden.
/**
* The distance in pixels from the top edge of this view's parent
* to the bottom edge of this view.
* {#hide}
*/
#ViewDebug.ExportedProperty(category = "layout")
protected int mBottom;
Question:
How can I extend the GridView without hitting that lamentations and without the usage of massive reflection? I mean it seems to be impossible to write that as a pure custom control.
How can I extend the GridView without hiting that limentations and without the usage of massive reflection?
Most likely, you don't. You copy the code into your project and modify to suit, including all requisite superclasses (up to ViewGroup). I will be stunned if you can achieve your aims by a simple subclass of GridView. You may even have to completely write your desired widget from scratch.
That was a terrible failior due I cannot access the com.android.internal.R class.
You will also need to copy over relevant resources, then fix up R references to point to your own resources.
but unforcantly they are hidden.
You find other ways of getting this data. mBottom, for example, can be changed to getBottom().

Is it crucial for performance to have ViewHolder as static in a ViewHolder pattern?

Is it crucial for performance to have ViewHolder as static in a ViewHolder pattern?
A ViewHolder object stores each of the component views inside the tag
field of the Layout, so you can immediately access them without the
need to look them up repeatedly. First, you need to create a class to
hold your exact set of views. For example:
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
It's not crucial for performance, it is about using. If ViewHolder class will not be static - you have to provide instance of parent class:
No enclosing instance of type Type is accessible.
Must qualify the allocation with an enclosing instance of type Type
(e.g. x.new A() where x is an instance of Type).
Edit: misunderstood the question -- it seems you are asking specifically about making it static. That shouldn't be crucial for performance, but the idea is every bit helps.
Final edit here: Static nested class in Java, why?
====
Orig answer below:
It's very nice to squeeze performance out of a heavy ListView (or some other type of recycled AdapterView). However the best way to tell would be to profile the performance somehow.
Also at Google IO 2010 they recommend this method:
http://www.youtube.com/watch?v=wDBM6wVEO70
Edit:
Also here's a link to traceview to profile the performance, though I'm unsure how well it works.
http://developer.android.com/tools/debugging/debugging-tracing.html
It's not mandatory to do that. But when you use to do like this, you are using the views again when your adapter view is null. You are creating a view and assigning values to the view part, and tag the whole view using static class ViewHolder. So when you come back and view is not null, then visible part will come from to get the tag. This is how you will create less object as well less work load on adapter.

android getting variables to constructor of custom view

android newbie here.
My first game involves a custom view, which is going to draw a game board and some scoreboards on the screen. I need to know how many players there are in order to get the number of scoreboards up, and I need this to be in known in the constructor of the custom view, so that appropriate variables are initialised on time.
My current implementation is like this, is this the correct way to get variables into the custom view constructor?...
I instantiate the custom view from my activity like this:
numPlayers=2;
setContentView(R.layout.gamescreen);
mBoardView = (BoardView) findViewById(R.id.board_view);
And in the custom view constructor:
public BoardView(Context context, AttributeSet attrs){
super(context, attrs);
vNumPlayers = ((GuappsXOMainGame)getContext()).getNumP();
That's how I have it now and it seems to work well enough, but is it better to be doing something along the lines of the answer to this question:?
android:how to instantiate my custom view with attributeset constructor
When a custom view (or SurfaceView) is the only view, often in games, then it is very easy to pass parameters like level, players, sound on etc by creating an instance of this view before setContentView(). An example:
MyView myView = new MyView(level, players, soundOn);
setContentView(myView);
In your case, your custom view is instantiated when the layout is created, hence you have to communicate back to the activity object to get the values. This is also possible. I have done this by setting static variables in the activity before calling setContentView(my_layout) then in the constructor for the custom view just say level = MyActivity.level
Or perhaps the way you are doing already where you obtain the instance of the activity and call a public method or variable.
In the link you provided an attribute is "hard-wired" into the XML layout. I don't see the advantage of that, when one can simply enter the value into the custom view class itself as a "final" variable.

Categories

Resources