public class CustomView extends SurfaceView {
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int itemWidth = (r - l) / getChildCount();
for (int i = 0; i < this.getChildCount(); i++) {
View v = getChildAt(i);
v.layout(itemWidth * i, 0, (i + 1) * itemWidth, b - t);
}
}
}
In the above overriden method getChildCount() and getChildAt(i) throws no such method found.
It is correct. SurfaceView extends View not ViewGroup. getChildCount() and getChildAt(i) are methods of ViewGroup
Related
I am trying to make my own list and therefore I am extending the AdapterView class.
I have overridden the onLayout method to add children, measure them and call the layout method.
my problem is that the onLayout method gets called infinitely and items are duplicated on each call.
I have looked on the Internet and verified that my children are not changing (I have made each child returns an empty view with no dynamic content).
here is my code:
protected void onLayout(final boolean changed, final int left, final int top, final int right,
final int bottom)
{
super.onLayout(changed, left, top, right, bottom);
// If we don't have an adapter yet, do nothing and return
if(mAdapter == null)
{
return;
}
fillList();
positionItems();
}
fillList():
private void fillList()
{
int position = 0;
if(mCurrentList*mNumberItemsPerList > mAdapter.getCount() )
{
mCurrentList= 0;
return ;
}
if(mCurrentList < 0)
{
double lastList = (double)(mAdapter.getCount()/mNumberItemsPerList);
mCurrentList= (int) Math.ceil(lastList);
return ;
}
//this.removeAllViewsInLayout();
while( position+mCurrentList*mNumberItemsPerList < mAdapter.getCount() )
{
// Child view
final View child = mAdapter.getView(position+mCurrentList*mNumberItemsPerList, null, this);
// Add the child and measure its dimensions to calculate the remaining space
addAndMeasureChild(child);
position++;
}
}
addAndMeasureChild():
private void addAndMeasureChild(View child)
{
LayoutParams params = child.getLayoutParams();
if(params == null)
{
params = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
}
//child.setLayoutParams(params);
addViewInLayout(child,-1, params);
// measure the dimensions
child.measure(MeasureSpec.EXACTLY | 250,MeasureSpec.EXACTLY | 250);
}
positionItems():
private void positionItems()
{
int left = 0;
int middleItem = mNumberItemsPerList / 2;
for(int index =0; index < getChildCount(); index++)
{
View child = getChildAt(index);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int bottom= (getHeight()-height)/2;
if(index < middleItem)
{
child.layout(left+15, 70+(middleItem-index)*30, left+width, 70+(middleItem-index)*30+height);
}
if(index == middleItem)
{
child.layout(left+15, 70, left+width, 70+height);
}
if(index > middleItem)
{
int diff = index -middleItem;
child.layout(left+15, 70+(middleItem-(index - (middleItem*diff)))*30, left+width, 70+(middleItem-(index - (middleItem*diff)))*30+height);
}
left += width;
}
}
and this is the getView of my child:
public View getView(final int position, final View convertView, final ViewGroup parent)
{
View view = convertView;
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.cat_item_layout, null);
}
return view;
}
I've figured out what was causing the infinite call to onLayout.
on my main layout I have added a clock with this custom view inside a relative layout.
I don't know why but the clock (which is updated every second) causes my view to call onLayout again.
In the view hierarchy:
Window > ViewGroup (root) > ViewGroup[...] > View (child)
I need to know root dimension in a profound child onMeasure event.
Exemple:
#Override
protected void onMeasure(int wMS, int hMS) {
int desiredW = Math.round(rootW * factorW);
int desiredH = Math.round(rootH * factorH);
/* ... compute final dimensions ... */
setMeasuredDimension(finalW, finalH);
}
Note: At this moment, getRootView and getWindow dimentions equals to 0 because children have to setMeasuredDimention before their parents
Considering a lot of children needing this dimension, to do it:
I created an interface:
public interface OnRootSizeChanged {
public void onRootSizeChanged(int w, int h);
}
I implemented my child which now implements OnRootSizeChanged inteface:
private int rootW;
private int rootH;
#Override
public void onRootSizeChanged(int w, int h) {
rootW = w;
rootH = h;
}
I implemented root view:
#Override
protected void onMeasure(int wMS, int hMS) {
int w = MeasureSpec.getSize(wMS);
int h = MeasureSpec.getSize(hMS);
dispatchOnRootSizeChange(this, w, h);
super.onMeasure(wMS, hMS);
}
private void dispatchOnRootSizeChange(ViewGroup v, int w, int h) {
for (int i = 0, n = v.getChildCount(); i < n; i++) {
View child = v.getChildAt(i);
if (child instanceof OnRootSizeChanged)
((OnRootSizeChanged) child).onRootSizeChanged(w, h);
if (child instanceof ViewGroup)
dispatchOnRootSizeChange((ViewGroup) child, w, h);
}
}
My question is:
Have I simpler way to do this without recursivity or with better practice ?
Update: This method is invalid in case of ViewPager element in ViewGroup[...] breabcrumb. When ViewPager instantiate children pages, they have not yet received OnRootSizeChanged event so:
Children have to ask the root dimension, no the root to tell his dimension to their children
So I searched how to target root from a profound child to ask him:
getRootView() not seems targeting the view attached with setContentView()
getWindow().getDecorView() either
One possible way is:
On child:
#Override
protected void onMeasure(int wMS, int hMS) {
ViewParent parent = getParent();
while (parent instanceof RootViewClass == false)
parent = parent.getParent();
RootViewClass root = (RootViewClass) parent;
int desiredW = Math.round(root.w * factorW);
int desiredH = Math.round(root.h * factorH);
/* ... compute final dimensions ... */
setMeasuredDimension(finalW, finalH);
}
On root instance of RootViewClass:
public int w, h;
#Override
protected void onMeasure(int wMS, int hMS) {
w = MeasureSpec.getSize(wMS);
h = MeasureSpec.getSize(hMS);
super.onMeasure(wMS, hMS);
}
But with lot of children, I don't think this is a good practice. If I could find root view without use of loop.
You can forward the parent's size by storing those values in the onMeasure() method as you receive them and then letting the children access the values in their onMeasure() method through the Context reference:
// simple interface
public interface ParentRef {
void YourViewGroup getRoot();
}
// the Activity implements the interface above
public class YourActivity extends Activity implements ParentRef {
private YourViewGroup mRoot;
//in onCreate initialize the mRoot reference
#Override
public YourViewGroup getRoot() {
return mRoot;
}
//... rest of the Activity
}
// the custom ViewGroup will store the dimensions:
//fields in the root view
private int mCurWidth;
private int mCurHeight;
#Override
protected void onMeasure(int wMS, int hMS) {
int w = MeasureSpec.getSize(wMS);
int h = MeasureSpec.getSize(hMS);
mCurWidth = w;
mCurHeight = h;
// now as the children are measured they can see the values above
super.onMeasure(wMS, hMS);
}
public int getStoredWidth() {
return mCurWidth;
}
public int getStoredHeight() {
return mCurHeight;
}
// in the children's onMeasure simply do:
#Override
protected void onMeasure(int wMS, int hMS) {
final YourViewGroup root = ((ParentRef) getContext()).getRoot();
//width root.getStoredWidth()
// height root.getStoredHeight()
/* ... compute final dimensions ... */
setMeasuredDimension(finalW, finalH);
}
You can use a ViewTreeObserver (http://developer.android.com/reference/android/view/ViewTreeObserver.html)
//...get your view, than attach a viewTreeObserver on it!
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//misure the view here, like view.getHeight()
}
});
I like the animation that occurs when scrolling through posts in the Google+ app, but I can't work out how they achieve it.
What techniques are employed to animate posts as they appear? I'm not looking for the animation itself, just how I'd apply any animation to a list of scrollable items.
Thanks.
After some testing I think I got something similar to work;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LinearLayout list = new LinearLayout(this);
list.setOrientation(LinearLayout.VERTICAL);
ScrollView scrollView = new ScrollView(this) {
Rect mRect = new Rect();
#Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
for (int i = 0; i < list.getChildCount(); ++i) {
View v = list.getChildAt(i);
// Tag initially visible Views as 'true'.
mRect.set(l, t, r, b);
v.setTag(getChildVisibleRect(v, mRect, null));
}
}
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
for (int i = 0; i < list.getChildCount(); ++i) {
View v = list.getChildAt(i);
mRect.set(getLeft(), getTop(), getRight(), getBottom());
// If tag == 'false' and View is visible we know that
// View became visible during this scroll event.
if ((Boolean) v.getTag() == false
&& getChildVisibleRect(v, mRect, null)) {
AlphaAnimation anim = new AlphaAnimation(0, 1);
anim.setDuration(1000);
v.startAnimation(anim);
v.setTag(true);
}
}
}
};
scrollView.addView(list);
for (int i = 0; i < 20; ++i) {
TextView tv = new TextView(this);
tv.setText("Test");
tv.setTextSize(72);
tv.setTextColor(Color.WHITE);
tv.setBackgroundColor(Color.GRAY);
list.addView(tv);
}
setContentView(scrollView);
}
Scrolling down the list should trigger alpha animation once new TextViews become visible.
There's a library for that, it seems to do the job well:
https://github.com/cuub/sugared-list-animations
Im having two custom viewgroups, superViewGroup and subViewGroup. The subviewgroup contains views. Im adding my superviewgroup to a linearLayout and the subViewGroups to my superviewgroup.
The superviewgroup onMeasure() is getting called but not in the subviewgroup. but in both cases onLayout() method is getting called.
The code as follows
public class SuperViewGroup extends ViewGroup{
public SuperViewGroup(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("boxchart","INSIDE ON MEASURE SUPER VIEWGROUP");
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
child.layout(0, 0, getWidth(), getHeight());
}
}
}
}
public class SubViewGroup extends ViewGroup{
public SubViewGroup(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("boxchart","INSIDE ON MEASURE SUB VIEWGROUP");
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
child.layout(0, 0, getWidth(), getHeight());
}
}
}
}
Comments are appreciated. thanks in advance.
Because you have to actually pass the measure to the children views:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("boxchart","INSIDE ON MEASURE SUPER VIEWGROUP");
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
//Make or work out measurements for children here (MeasureSpec.make...)
measureChild (child, widthMeasureSpec, heightMeasureSpec);
}
}
}
Otherwise you never actually measure your children. It is up to you to decide how to do this. Just because your SuperViewGroup is in a linear layout, your SuperViewGroup takes on responsibility to measure its children.
What is the proper way to override onLayout method in a custom layout extending the RelativeLayout?
I'm trying to place all views in sort of a table. The idea is to fill one row with ImageViews until it's full and then continue in the new row.
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int idOfViewToTheLeft = 1;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getContext(),);
ImageView bookmark;
for(int counter = 1; counter < getChildCount(); counter++) {
bookmark = (ImageView) findViewById(counter);
if(counter > 1) {
if(this.getWidth() > (bookmark.getLeft() + bookmark.getWidth())) {
params.addRule(RelativeLayout.RIGHT_OF, bookmark.getId() - 1);
} else {
params.addRule(RelativeLayout.BELOW, idOfViewToTheLeft);
idOfViewToTheLeft = bookmark.getId();
}
}
bookmark.setScaleType(ScaleType.CENTER_CROP);
}
}
}
I explain how to write custom layouts (and in particular a FlowLayout, which is what you want to do it seems like) in this presentation
Video available here.