Search code examples
androidandroid-layoutandroid-contextandroid-custom-attributes

Properly acessing custom attributes of custom LinearLayout


So I'm playing around with android and got to a dead-end.

This question may be related to this one, since its answer could be a good workaround for my problem, but I'm interested on the reason why my code is failing. I may have some severe misconceptions about Views.

So, I have this backgammon board:

backgammon board with chips stacked on the top and the bottom

I will want to perform animations on every move, and for that I'll need to get the moving chip's origin and destination coordinates on the screen.

I have therefore to be sure that points behave as stacks, new views have to be inserted on the bottom of the upside-down points, and in the top of downside-ups. Since the gravity of any point is TOP or BOTTOM depending on it's position on the screen, I would be able to do this getting this attribute, but it seems to be private: see this answer in the related post

So I created a custom LinearLayout :

<resources>
<declare-styleable name="PointView">
    <attr name="upsideDown" format="boolean" />
</declare-styleable>
</resources>

Which overrides LinearLayout

public class PointView extends LinearLayout {
    private boolean mUpsideDown = false;

public PointView(Context context) {
    super(context);
}

public PointView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs, 0);
}

public PointView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context, attrs, defStyle);
}

private void init(Context context, AttributeSet attrs, int defStyle) {

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PointView);

    mUpsideDown = a.getBoolean(R.styleable.PointView_upsideDown, false);
    a.recycle();
}

@Override
public View getChildAt(int index) {
    return mUpsideDown ? super.getChildAt(index) : super.getChildAt(super.getChildCount() - index + 1);
}

@Override
public void addView(View child) {
    if(mUpsideDown) {
        super.addView(child);
    }
    else {
        super.addView(child, 0);
    }
}
}

They're included in my Activity's XML like this:

<spacebar.backgammoid.PointView
                    android:orientation="vertical"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:background="@drawable/black_point"
                    android:layout_weight="1"
                    android:focusableInTouchMode="false"
                    android:id="@+id/point13"
                    android:weightSum="5"
                    android:clipChildren="false"
                    android:gravity="top"
                    app:upsideDown="true"/>

Now, when init() is called during the runtime, I get an exception:

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.resolveLayoutParams()' on a null object reference

Looks like my context is null in :

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PointView);

And I don't have any clue about why, and so I can't access my custom attribute upsideDown.

Trying to get access to the property from attrs :

        attrs.getAttributeBooleanValue(R.styleable.PointView_upsideDown, false);

Which looks like a synonym to me, although it's clearly not, is always returning true.

Any Idea?

Thanks in advance

[EDIT]

The exception's stacktrace :

--------- beginning of crash
06-23 10:04:34.972    2036-2036/spacebar.backgammoid E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: spacebar.backgammoid, PID: 2036
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.resolveLayoutParams()' on a null object reference
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.ViewGroup.resolveLayoutParams(ViewGroup.java:6116)
        at android.view.View.setLayoutParams(View.java:11473)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:273)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
        at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3055)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2395)
        at android.app.ActivityThread.access$800(ActivityThread.java:151)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5257)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

[EDIT 2]

I was populating the points with chips programmatically, populating in the Activity's XML throws the same exception on design-time.

<spacebar.backgammoid.PointView ...>
    <ImageView ... />
</spacebar.backgammoid.PointView>

Yet, if I use regular LinearLayouts instead of my custom view everything works fine, and there's no error.

<LinearLayout ...>
    <ImageView ... />
</LinearLayout>

Also, , I don't know if it's relevant, but the android-studio designer doesn't allow me to nest Views in my PoinView:


Solution

  • Okay, I got it working

    There are two issues in my code.

    1. Murphy's Ninth Law: Nature always sides with the hidden flaw: This is just wrong

      return mUpsideDown ? super.getChildAt(index) : super.getChildAt(super.getChildCount() - index + 1);

    I was trying to access two positions after the end of the children list. It should be

    super.getChildAt(super.getChildCount() - (index + 1));
    

    or

    super.getChildAt(super.getChildCount() - index - 1);
    

    That was the reason for the exception. Android was trying to inflate null at that point, when populating my LinearLayout.

    Secondly,

    @Override
    public void addView(View child) {
        if(mUpsideDown) {
            super.addView(child);
        }
        else {
            super.addView(child, 0);
        }
    }
    

    Was not working, not overriding this method gives me the expected behavior, I guess android uses getChildAt() when inflating LinearLayouts, and thus I can insert children views the regular way so they stack in the top of the point regardless of its orientation.

    The way I was accessing my custom attributes was OK (which is the answer of my question as it was formulated)

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PointView);
    
    mUpsideDown = a.getBoolean(R.styleable.PointView_upsideDown, false);
    a.recycle();
    

    Thanks, @Ravi Thapliyal, for your interest