I am new to android, and it seems only viewgroup can be parent of some View, but when I use DDMS to dump view hierarchy of an app, I find there is a View containing some children. Could anyone please explain this to me? Please see the image below:
To investigate on that issue I created a custom ViewGroup that does nothing:
private static class MyViewGroup extends ViewGroup {
public MyViewGroup(Context _context) { super(_context); }
#Override
protected void onLayout(boolean _changed, int _l, int _t, int _r, int _b)
{ /* nothing... */ }
}
and added it to the current content view of an activity:
ViewGroup foo = (ViewGroup)
this.getWindow().getDecorView().getRootView().findViewById(android.R.id.content);
foo.addView(new MyViewGroup(this));
The hierarchy viewer of eclipse shows it labeled as a 'View' too.
Furthermore I looked at the Node details of another 'View' with Children and was able to see a resource-id.
In my case it had the value android:id/action_bar_overlay_layout and I could find that resource-id to be used in an XML-layout for a android.support.v7.internal.widget.ActionBarOverlayLayout component.
It's implementation is in the support library and truely extends Framelayout.
I think this issue is simply the hierarchy viewer only supporting labels for standard view components.
This is just a guess, but I think that any ViewGroup that is not one of the ViewGroup-implementations provided by Android will be shown as View, maybe to hide the name of your custom ViewGroups from others. So it is basically a privacy thing for the developers
Related
Bacground
I have been working on stripping out a library that deals with adding Accessibility with Talkback that I have created in an existing app. Originally my custom views were all ViewGroups, so I got everything working amazingly with ViewGroups (focusable navigation with D-pad, initial view focus, and content descriptions)
When I was moving this to a standalone library, I noticed that it didn't work with View. I thought ViewGroup was the superclass, but it turns out that View is the superclass. So I have been trying to find some workarounds to fix my issue. I started to do the following, and have a question based on this approach...
Code In Question
public class Accessibility {
public static ViewGroupAccessibility with(ViewGroup viewGroup) {
return new ViewGroupAccessibility(viewGroup);
}
public static ViewAccessibility with(View view){
return new ViewAccessibility(view);
}
}
I have fully implemented ViewGroupAccessibility and I intend to fully implement ViewAccessibility as it is a stub right now. So far the below code works well with TalkBack, I can do ViewGroup related stuff with ViewGroups, and it appears that I can do View related stuff with Views; however, I am wondering if this is even needed
What I know
Accessibility.with(new RelativeLayout(...)) // Returns ViewGroupAccessibility as RelativeLayout is a ViewGroup
//
...will return a ViewGroupAccessibility that can handle ViewGroup related stuff that can contain many different View and ViewGroup. (See code at the bottom of this post for real usage, and what what methods are available for ViewGroupAccessibility)
Accessibility.with(new Button(...)) // Returns ViewAccessibility as Button is a View
//
...will return a ViewAccessibility that can handle single View only related stuff (that is my assumption). Think only a Button.
What I don't know
// Hypothetical Usage
Accessibility
.with(new ClassThatExtendsView_WithMultipleComponentsThatCanHaveAccessibilitySetOnEachComponentIndividually(...));
// Custom View that extends View
public class ClassThatExtendsView_WithMultipleComponentsThatCanHaveAccessibilitySetOnEachComponentIndividually extends View {
...
}
Is this even possible? If no, then I am good. If yes, then I have a lot extra to think about
It will return a ViewAccessibility that can handle single View only, but then that would be the wrong thing to return.
Another way of asking the question is am I guaranteed that if a user calls Accessibility.with(View) that the given view will ALWAYS be a single view only? Like Just a single Button. Or can the View be made of more than one component
Full Code
You can check out the code at https://codereview.stackexchange.com/questions/134289/easily-add-accessibility-to-your-app-as-an-afterthought-yes-as-an-afterthought (there is also a GitHub link to the original code). I go in incredible detail into how the project was started, my design decisions, and my future goals all to help guide the code review process.
However, here is a snippet of a usage I have for ViewGroup
public class ContributionView extends RelativeLayout implements Mappable<Resume.Contribution> {
// Called from Constructors
private void init(AttributeSet attrs, int defStyle) {
root = (ViewGroup) LayoutInflater.from(getContext()).inflate(
R.layout.internal_contribution_view, this, true);
...
// Declare Navigation Accessibility
Accessibility.with(root)
// Disable certain views in the ViewGroup from ever gaining focus
.disableFocusableNavigationOn(
R.id.contribution_textview_creator,
R.id.contribution_textview_year)
// For all focusable views in the ViewGroup, set the D-pad Navigation
.setFocusableNavigationOn(txtProjectName)
.down(R.id.contribution_textview_description).complete()
.setFocusableNavigationOn(txtContributionDescription)
.up(R.id.contribution_textview_name)
.down(R.id.contribution_textview_link).complete()
.setFocusableNavigationOn(txtProjectLink)
.up(R.id.project_textview_description).complete()
// Set which view in the ViewGroup will have be first to be focused
.requestFocusOn(R.id.contribution_textview_name);
invalidateView();
}
private void invalidateView() {
...
// Declare Content Description Accessibility
Accessibility.with(root)
// Set the content descriptions for each focusable view in the ViewGroup
// Set the content description for the Contribution Name
.setAccessibilityTextOn(txtProjectName)
.setModifiableContentDescription(getProjectName())
.prepend("Contribution occurred on the Project called ")
.append(String.format(" by %s in %s",
getProjectCreator(),
getContributionYear())).complete()
// Set the content description for the Contribution Description
.setAccessibilityTextOn(txtContributionDescription)
.setModifiableContentDescription(getContributionDescription())
.prepend("Description: ").complete()
// Set the content description for the Contribution URL
.setAccessibilityTextOn(txtProjectLink)
.setModifiableContentDescription(getProjectLink())
.prepend("URL is ").complete();
}
...
}
Yes, there is a way to move accessibility amongst the various areas/components of a View. It requires a little work, though.
Start here:
https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeProvider.html
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);
}
I'm trying to add a view in a viewGroup (without xml) but can't make the view appear. I can't figure out what I'm missing...please help, I've been looking all the web for hours now.
Here is my code (the Background class extends ViewGroup) :
public void setupBackground()
{
this.backgroundView = new Background(activity);
View bgGround = new View(activity);
bgGround.setX(100);
bgGround.setY(200);
bgGround.setBackgroundColor(activity.getResources().getColor(R.color.black));
bgGround.setBackgroundResource(R.drawable.mario_ground);
this.backgroundView.addView(bgGround, 100,100);
this.activity.addContentView(backgroundView, new LayoutParams(bgWidth,bgHeight));
}
Ok, I found it. ViewGroup is not enough for drawing content, I had to extend RelativeLayout (or another kind of layout) instead.
What made me loose lot of time is that I had overrid onLayout method while still in ViewGroup and then changed to RelativeLayout, so the onLayout for RelativeLayout wasn't calling super.onLayout.
Thanks anyway, and sorry for my poor english :)
I have following issue.
I have two fragments, each of them have a ListView with adapter. By choosing element from first list, I replace the fragment containing first list with fragment containing second list. I want to make each row background transparent only in second Fragment, but the following code used in getView of the second adapter, it looks like it updates the resource? Could anyone explain why?
Code:
First Fragment:
public View getView(int position, View convertView, ViewGroup parent) {
(...)
v.setBackgroundResource(R.drawable.abstract_gray);
(...)
}
Second Fragment:
public View getView(int position, View convertView, ViewGroup parent) {
(...)
Drawable backgroundDrawable = context.getResources().getDrawable(R.drawable.abstract_gray);
backgroundDrawable.setAlpha(ToolsAndConstants.BACKGROUND_TRANSPARENCY);
v.setBackgroundDrawable(backgroundDrawable);
// I call this to check if when I call the drawable from resurce it will not be transparent
v.setBackgroundResource(R.drawable.abstract_gray);
// But it is...So previous fragment background, when I go back with back Button
(...)
}
Maybe this question is really basic, but when I create new Object from resource, I actually work with the resource itself and all changes made on new Object will affect whole application, even if other classes will not have access to that Object??
EDIT:
Sorry, I just realized that I don't create new Drawable, I just make a reference. How can I create new one then? If I cant, how can I change background of only second list?
Marek read this article, it explains your doubts http://android-developers.blogspot.com/2009/05/drawable-mutations.html
Documents say:
When the content for your layout is dynamic or not pre-determined, you
can use a layout that subclasses AdapterView to populate the layout
with views at runtime. A subclass of the AdapterView class uses an
Adapter to bind data to its layout.
But most of tutorials are in about ListView,GridView,Spinner and Gallery.
I'm looking to extend a subclass directly from AdapterView. I have to create a custom view that it's content is dependent to an adapter.
How can I do this, and what methods must be overridden?
First, you should be absolutely sure that AdapterView is what you want, because not all "dynamic or not pre-determined" views can be implement via AdapterView. Sometimes you'd better create your view extending ViewGroup.
if you want to use AdapterView, take a look at this really nice example. There are a lot of custom views with adapter on GitHub. Check out this one (extends ViewGroup).
This may not be a total answer to your question but i am showing you most probably a starting point or pointer which can guide you:
Sony Developer Tutorials - 3D ListView
ListView extends AbsListView which in turn extends AdapterView<ListAdapter>. So if you absolutely must implement such a custom view from scratch, you could have a look at the source code of those classes:
ListView
AbsListView
AdapterView
But beware, that's quite a task. Perhaps it may be sufficient to use one of the existing classes and tweak the look.
Deriving from AdapterView can work, but it may not be as beneficial as you hope. Some of the infrastructure provided by AdapterView is package-private, meaning we don't have access to it.
For example, AdapterView manages the selected item index for AbsListView and ListView. However, because methods like setNextSelectedPositionInt(int position) (which is the only path to setting mNextSelectedPosition) are package-private, we can't get to them. AbsListView and ListView can get to them because they're in the same package, but we can't.
(If you dig into the AdapterView source you'll find that setNextSelectedPositionInt() is called from handleDataChanged(). Unfortunately handleDataChanged() is also package-private and is _not_called from anywhere else within AdapterView that could be leveraged to enable setting position.)
That means that if you need to manage selected position, you'll need to recreate that infrastructure in your derived class (or you'll need to derive from ListView or AbsListView...though I suspect you'll run into similar problems deriving from AbsListView). It also means that any AdapterView functionality that revolves around item selection likely won't be fully operational.
You could create something like this :
public class SampleAdapter extends BaseAdapter {
public SampleAdapter() {
// Some constructor
}
public int getCount() {
return count; // Could also be a constant. This indicates the # of times the getView gets invoked.
}
public Object getItem(int position) {
return position; // Returns the position of the current item in the iteration
}
public long getItemId(int position) {
return GridView.INVALID_ROW_ID;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
view = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.some_layout, null);
view.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
view.setBackgroungColor(Color.RED);
return view;
}
}
And this could be invoked like :
GridView sampleView = (GridView) linearLayout.findViewById(R.id.sample_layout);
sampleView.setAdapter(new SampleAdapter());