24

I did as follows

1) Creating a styleable

<declare-styleable name="Viewee">
    <attr name="linkedView" format="reference"/>
</declare-styleable>

2) defining custom view layout

<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#ffc0">
    <TextView
            android:id="@+id/custom_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="[text]"
            />
</LinearLayout>

3) Creating required class

public class Viewee extends LinearLayout
{
public Viewee(Context context, AttributeSet attributeSet)
{
    super(context, attributeSet);
    View.inflate(context, R.layout.viewee, this);
    TextView textView = (TextView) findViewById(R.id.custom_text);
    TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.Viewee);
    int id = typedArray.getResourceId(R.styleable.Viewee_linkedView, 0);
    if (id != 0)
    {
        View view = findViewById(id);
        textView.setText(((TextView) view).getText().toString());
    }

    typedArray.recycle();
}
}

and finally in an activity like below

<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.ns"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical">
    <TextView
            android:id="@+id/tvTest"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="android"/>
    <com.ns.Viewee
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            app:linkedView="@+id/tvTest"
            />
</LinearLayout>

now although I recieve a non zero id in Viewee constractor, findViewById(id) retuns null and NullPointerException occures.

what am I missing?

I did it as described here

Community
  • 1
  • 1
anonim
  • 2,494
  • 6
  • 30
  • 40

3 Answers3

21

I found the answer!

The issue was with findViewById(id) and where I called it. findViewById only looks for a child view not a view exist on upper hierarchy level as documentation says . So I have to call something like getRootView().findViewById(id) but that also returns null becase where I called it was not corrent.

In Viewee constractor Viewee itself has not attached to its root yet so that call causes NullPointerException.

So If I call to getRootView().findViewById(id) somewhere else after constraction, it works fine and both "@+id/tvTest" and "@id/tvTest" are correct. I've tested it!

the answer is as follows

public class Viewee extends LinearLayout
{
    public Viewee(Context context, AttributeSet a)
    {
        super(context, attributeSet);
        View.inflate(context, R.layout.main6, this);
        TextView textView = (TextView) findViewById(R.id.custom_text);
        TypedArray t = context.obtainStyledAttributes(a, R.styleable.Viewee);
        int id = t.getResourceId(R.styleable.Viewee_linkedView, 0);
        if (id != 0)
        {
            _id = id;
        }

        t.recycle();
    }

    private int _id;

    public void Foo()
    {
        TextView textView = (TextView) findViewById(R.id.custom_text);
        View view = getRootView().findViewById(_id);
        textView.setText(((TextView) view).getText().toString());
    }
}

and Foo is called when it is required to process the attached view via its reference id somewhere else in your activity and the like.

The credit completely goes to those guys contributed in this post. I had not seen that post before submitting the question.

Community
  • 1
  • 1
anonim
  • 2,494
  • 6
  • 30
  • 40
  • do NOT place it in `onDraw` but in `onAttachedToWindow`. Placing it in onDraw will called X times per second the method, could be 60 times per seconds. @DanielWilson can you remove your comment? – Hugo Gresse Jan 21 '16 at 16:36
18

I know this is an old question, but I thought I would add another way of doing this as I wanted to encapsulate everything into my custom view.

Instead of calling from the outside, another way of getting a view higher up in the hierarchy, I've hooked into onAttachedToWindow() instead:

public class MyCustomView extends LinearLayout {
    private int siblingResourceId;
    private View siblingView;

    public MyCustomView(Context context, AttributeSet a) {
        super(context, attributeSet);
        inflate(context, R.layout.main6, this);
        TextView textView = (TextView) findViewById(R.id.custom_text);
        TypedArray t = context.obtainStyledAttributes(a, R.styleable.Viewee);
        siblingResourceId = t.getResourceId(R.styleable.MyCustomView_siblingResourceId, NO_ID);
        t.recycle();
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (siblingResourceId != NO_ID) {
            siblingView = ((View) getParent()).findViewById(siblingResourceId);
        }
    }
}

onAttachedToWindow is called quite early, but apparently late enough for the whole view hierarchy to have settled. It works flawless for my needs at least and is a little bit more controlled and doesn't need interaction from outside to work ;-)

EDIT: Kotlin code added

class MyCustomView(context: Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {
    private val siblingResourceId: Int
    private lateinit var siblingView: View

    // All other constructors left out for brevity.

    init {
        inflate(context, R.layout.main6, this)
        val textView = findViewById<TextView>(R.id.custom_text)
        val t = context.obtainStyledAttributes(a, R.styleable.Viewee)
        siblingResourceId = t.getResourceId(R.styleable.MyCustomView_siblingResourceId, NO_ID)
        t.recycle()
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        if (siblingResourceId != NO_ID) {
            siblingView = (parent as View).findViewById(siblingResourceId) 
        }
    }
}

Note: We assume that the parent of this custom View is a View itself.

Darwind
  • 7,284
  • 3
  • 49
  • 48
  • If there was other view with same id in upper level this will get that view – Ali mohammadi Aug 06 '18 at 12:58
  • @Alimohammadi not sure if that makes sense - how can you build a layout with 2 of the same ids? It shouldn't be possible. – Darwind Aug 06 '18 at 19:06
  • Think about fragment in activity .consider we have same id in activity.xml and fragment.xml file and this return view that is in activity – Ali mohammadi Aug 07 '18 at 04:55
  • @Alimohammadi it's not relevant as we're inside a custom view, not a `Fragment` or an `Activity` for that matter. This is what the docs says about `findViewById` inside a `View` class: Finds the first descendant view with the given ID, the view itself if the ID matches {@link #getId()}, or {@code null} if the ID is invalid (< 0) or there is no matching view in the hierarchy. – Darwind Aug 17 '18 at 22:56
2

Your described android:id is set to app:linkedView="@+id/tvTest. However, @+id/tvTest is used to create a new id with name "tvTest". What you want to do is use app:linkedView="@id/tvTest.

Jason L
  • 1,812
  • 2
  • 22
  • 43
  • I applied your answer though `View view = findViewById(id);` still returns null! – anonim Jun 14 '12 at 20:21
  • Why are you using `id` to hold the id of the View? It should be fairly straightforward to use `R.id.foo` instead of creating another variable. Also, I can't tell through your comment whether you changed the `app:linkedView` attribute of your `` element to `"@id/tvTest`. – Jason L Jun 14 '12 at 20:55
  • I have to hold the `id` because `Viewee` is a custom view refering to another view for a reson. I can not refer to the linked view as `R.id.foo` as you said. what if I use `Viewee` in another layout where `R.id.foo` not exist? I also did as you recommended and changed `"@+id/tvTest"` to `"@id/tvTest"` but with no success. – anonim Jun 14 '12 at 21:37
  • @+id instead of @id is not relevant, plus sign is fine – Kamil Seweryn Sep 24 '15 at 12:41