I have made a custom ScrollView which has a single child inside it (a layout). Finding the child by ID using findViewByid in the CustomScrollView.java causes the app to crash, so is it possible to find it in a simple manner similar to this (which detects the custom scrollview) or getParent() (which detects the parent layout that both the scrollview and the child is inside of)?
I'm trying to apply requestDisallowInterceptTouchEvent(true); to the child of the custom ScrollView.
xml:
<?xml version="1.0" encoding="utf-8"?>
<com.example.test4.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/dynamic_status_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
tools:context=".MainActivity">
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/dynamic_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:context=".MainActivity">
</android.support.constraint.ConstraintLayout>
</com.example.test4.CustomScrollView>
Code inside CustomScrollView.java where I want to add requestDisallowInterceptTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Add here
return super.onTouchEvent(ev);
}
Problem:
In short, I want to find the child of the custom ScrollView, the layout with the id dynamic_status, without using findViewById.
You should be able to do it like this:
#Override
public boolean onTouchEvent(MotionEvent ev) {
((ConstraintLayout)getChildAt(0)).requestDisallowInterceptTouchEvent(true);
return super.onTouchEvent(ev);
}
You can always access child Views (Views nested deeper than your current View) with getChildAt. Since requestDisallowInterceptTouchEvent is only available to ViewGroups you have to cast it either directly to your ConstraintLayout or - for better reuseability - to a ViewGroup.
Please note that I had not included any checks if the child is available.
This code is also the same for Kotlin and Java. It does not make any difference (syntactically yes, but the concept is the same).
Related
I want to create a wrapper element that can be reusable throughout the whole app, because it's the base of each screen.
This wrapper should be possible to add to layout editor in Android Studio and needs to be able to hold any inner elements inserted in editor.
But these elements have to be inserted inside the layout under header with image and only inside a specified area defined in wrapper xml layout (innerContainer).
This is the layout of wrapper:
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
...
android:background="#drawable/frame_container_layout"
tools:context=...>
<include
android:id="#+id/cv_header"
layout="#layout/cv_header"
...>
</include>
<android.support.constraint.ConstraintLayout
android:id="#+id/innerContainer"
.../>
</android.support.constraint.ConstraintLayout>
What I need is to use this layout in custom view in a way, that when it's used somewhere, the elements will be only inserted into innerContainer.
Child XML:
<...custom_views.WrapperFragmentView
...
app:exampleColor="#33b5e5"
app:exampleDimension="24sp"
app:exampleDrawable="#android:drawable/ic_menu_add"
app:exampleString="Hello, WrapperFragmentView">
<TextView
android:id="#+id/textView1"
...
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</...custom_views.WrapperFragmentView>
The child is now inserted over the wrapper layout, which is what I don't want. It's not inside innerContainer.
In kotlin file I defined
class WrapperFragmentView : android.support.constraint.ConstraintLayout {
...
private fun init(attrs: AttributeSet?, defStyle: Int) {
var inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.cv_wrapper_fragment, this, true)
findViewById<android.support.constraint.ConstraintLayout>(R.id.innerContainer)
The innerContainer is the view I want to have as a root element for the children that are going to use it. Not the whole layout cv_wrapper_fragment.
How to set this in the WrapperFragmentView?
Or, if the whole concept is wrong and if there is an easier approach how to work with reusable wrapping elements I am all for that.
In my Player activity I am trying to add a view from a different XML by calling addView:
view = (ViewGroup)findViewById(R.id.player_ConstraintLayout);
notifierView = LayoutInflater.from(view.getRootView().getContext())
.inflate(R.layout.notifier, null);
view.addView(notifierView);
// view.invalidate();
The Player activity comes with this associated XML file:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/player_ConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.android.Player">
...
</android.support.constraint.ConstraintLayout>
And the one I want to add is notifier.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000"
android:translationZ="9dp"
android:elevation="9dp">
</android.support.constraint.ConstraintLayout>
I have debugged the app and by inspecting variable view I can confirm that the notifierView has been added and is in the children list of the view. However the notifierView is not shown, even I have expected it to be of full screen size and red. When I inspected notifierView in debugger, it showed its mTop, mBottom, mLeft, and mRight variables being all 0. I assuming it means that the
android:layout_width="match_parent"
android:layout_height="match_parent"
have been ignored? Or is there some other reason why I don't see this notifierView at all?
EDIT/UPDATE:
Let me show you more of the code, as the previous above is a little simplified one.
In Player activity I have:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
notifier = new Notifier((ViewGroup)findViewById(R.id.player_ConstraintLayout));
...
}
and in Notifier.java I have
Notifier(ViewGroup view) {
handler = new android.os.Handler();
LayoutInflater inflater = LayoutInflater.from(view.getRootView().getContext());
notifierView = inflater.inflate(R.layout.notifier, view, false);
}
ConstraintLayout doesn't actually support match_parent for its children. You have to use this for "match parent" width:
android:layout_width="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
And this for "match parent" height:
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
Additionally, your LayoutInflater.inflate() call should be changed. When you pass null as the second argument, you don't give the system enough information to understand how to parse layout_ attributes on the inflated view; you should pass a real parent (in this case, view). However, if you change null to view, the return value of the inflate() call will change, so you have to add an additional param (attachToRoot) to make sure the rest of the semantics don't change.
LayoutInflater inflater = LayoutInflater.from(view.getRootView().getContext());
notifierView = inflater.inflate(R.layout.notifier, view, false);
(Posted solution on behalf of the question author).
Invalidate Caches / Restart helped. Android Studio sometime gets things wrong.
Since I began to create the inheritors from Android Views, I frequently see the error Binary XML file line XX: Error inflating class. In this special case it will be the BottomSheet.
public class MyBottomSheet extends TableLayout {
BottomSheetBehavior myBottomSheetBehavior;
ImageView testImageView;
public MyBottomSheet(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
testImageView = (ImageView) findViewById(R.id.imageView); // still OK...
myBottomSheetBehavior = BottomSheetBehavior.from(this); // crashes Here
testImageView.setOnClickListener(onClickListener); // or here if to remove the previous row
}
// ...
}
My app has multiple tabs with individual fragments. The BottomSheet requires only in one of them, however if I dont't include the BottomSheet in the MainActivity markup, it will not move. So the code is:
activity_main.xml
<?xml version="1.0"encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- ... -->
<include layout="#layout/my_bottom_sheet"/>
</android.support.design.widget.CoordinatorLayout>
</android.support.v4.widget.DrawerLayout>
my_bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.bottomsheettest.widgets.MyBottomSheet
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/myBottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="*"
tools:context="com.example.bottomsheettest.MainActivityFragment"
tools:showIn="#layout/activity_main">
<TableRow>
<ImageView
android:id="#+id/imageView"
android:src="#mipmap/ic_launcher"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
</TableRow>
</com.example.bottomsheettest.widgets.MyBottomSheet>
MainActivityFragment.java
public class MainActivityFragment extends Fragment {
private static final int LAYOUT = R.layout.fragment_main;
private MainViewFAB fab;
private MyBottomSheet addNewItemToInboxBottomSheet;
public MainActivityFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
MainActivity activity = (MainActivity)getActivity();
fab = activity.getMainViewFab();
addNewItemToInboxBottomSheet = (MyBottomSheet) activity.findViewById(R.id.myBottomSheet);
return inflater.inflate(LAYOUT, container, false);
}
}
I supposeI missed something important about Android Views inheritance. But this case especially strange. In the MyBottomSheet class, there are no problem when getting the ImageView from the BottomSheet. Consequently, the access to BottomSheet from the respective class is available. However, if try to manipulate somethig related with BottomSheet, the exception will be.
I share the source code (.zip) created in the Android Studio during accept the some answer. There are no tabs in shared app, however I kept the fragment structure because it exists in the original app.
When creating a View subclass that is meant to be inflated from a layout, there are several things that won't be available in the constructor at runtime.
For one, child Views of your custom View defined in the layout will not have been inflated and attached yet, so any attempt to get references to them with findViewById() will return null there. Instead, this should be done no earlier than onFinishInflate(), and moving your ImageView initialization to that method will solve that particular error.
Also, your custom View itself will not have had its own LayoutParams set yet in the constructor. This is causing the issue with the BottomSheetBehavior.from() call there, since that method simply retrieves the Behavior from the LayoutParams constructed during inflation from layout attributes on the View. In the onAttachedToWindow() method, the LayoutParams will have been set, and you can make the BottomSheetBehavior.from() call there.
Lastly, I would just mention that an InflateException is thrown whenever anything goes wrong with inflation. The root cause of the failure will be listed further on in the stack trace, and you should look there to find the more relevant details.
I'm creating an app which used a GridView to display a set of items. Each of these items is defined as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.myapp.customviews.Tile
android:layout_width="wrap_content"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_height="match_parent"
android:id="#+id/tile" />
</LinearLayout>
In my OnItemClickHandler I want to check if the type of the View with id R.id.tile (My custom View class mentioned above) is com.example.myapp.customviews.EmptyTile. This class is a child of the Tile class.
Currently I'm checking if the class is an EmptyTile as follows:
LinearLayout tileLayout = (LinearLayout) gridView.getChildAt(position);
if (EmptyTile.class.isInstance(tileLayout.findViewById(R.id.tile))) {
Log.d("myappDebug", "Correct instance!");
}
I have tried checking using instanceof and tileLayout.findViewById(position).getClass() == EmptyTile.class but these also don't work. At runtime, my view is just a Tile View and not an EmptyTile View.
How can I check if my View is actually an instance of the EmptyTile class?
instanceof must work. But, in your XML, you define the view to be of type com.example.myapp.customviews.Tile, so why do you expect it to be an EmptyTile?
If you use EmptyTile (with the correct package of course) in the XML, the instanceof check and the other options will work.
I'm currently using the XML layout to set my Activity background. How can I do this in the Java code instead?
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/bg" >
Before calling Activity.setContentView(View) use one of the setBackground...() methods on your main View.
Set an id attribute to your parent RelativeLayout. Than find it by id in activity code and use setBackgroundResource method.