自定义SearchView切换布局属性时触发NullPointerException问题
问题:自定义SearchView操作外部Toolbar时触发NullPointerException
我正在实现一个带动画效果的自定义SearchView,在activity_location.xml布局中使用。因为布局里的cl_ToolBar会和SearchView重叠,所以我想在调用openSearch()时把cl_ToolBar设为INVISIBLE,调用closeSearch()时设为VISIBLE,但现在点击按钮时出现了NullPointerException。
错误日志
FATAL EXCEPTION: main Process: com.virusnetic.meather, PID: 11427 java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.constraintlayout.widget.ConstraintLayout.setVisibility(int)' on a null object reference at com.virusnetic.meather.views.SearchView._init_$lambda$0(SearchView.kt:26) at com.virusnetic.meather.views.SearchView.$r8$lambda$Mcmg8MRJAUGTfnVTINGMDsQmb5g(Unknown Source:0) at com.virusnetic.meather.views.SearchView$$ExternalSyntheticLambda0.onClick(Unknown Source:2) at android.view.View.performClick(View.java:7441) at android.view.View.performClickInternal(View.java:7418) at android.view.View.access$3700(View.java:835) at android.view.View$PerformClick.run(View.java:28676) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7839) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
相关代码
SearchView.kt
package com.virusnetic.meather.views import android.animation.Animator import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.view.ViewAnimationUtils import android.widget.EditText import android.widget.FrameLayout import android.widget.RelativeLayout import androidx.constraintlayout.widget.ConstraintLayout import com.virusnetic.meather.R class SearchView( context: Context, attrs: AttributeSet ) : FrameLayout(context, attrs) { init { LayoutInflater.from(context) .inflate(R.layout.view_search, this, true) findViewById<View>(R.id.open_search_button).setOnClickListener { // Set Location toolbar to Invisible findViewById<ConstraintLayout>(R.id.cl_ToolBar).visibility = View.INVISIBLE openSearch() } findViewById<View>(R.id.close_search_button).setOnClickListener { closeSearch() // Set Location toolbar to GONE findViewById<ConstraintLayout>(R.id.cl_ToolBar).visibility = View.VISIBLE } } private fun openSearch() { val open_search_button: View = findViewById(R.id.open_search_button) val search_input_text: EditText = findViewById(R.id.search_input_text) val search_open_view: RelativeLayout = findViewById(R.id.search_open_view) search_input_text.setText("") search_open_view.visibility = View.VISIBLE val circularReveal = ViewAnimationUtils.createCircularReveal( search_open_view, (open_search_button.right + open_search_button.left) / 2, (open_search_button.top + open_search_button.bottom) / 2, 0f, width.toFloat() ) circularReveal.duration = 300 circularReveal.start() } private fun closeSearch() { val open_search_button: View = findViewById(R.id.open_search_button) val search_input_text: EditText = findViewById(R.id.search_input_text) val search_open_view: RelativeLayout = findViewById(R.id.search_open_view) val circularConceal = ViewAnimationUtils.createCircularReveal( search_open_view, (open_search_button.right + open_search_button.left) / 2, (open_search_button.top + open_search_button.bottom) / 2, width.toFloat(), 0f ) circularConceal.duration = 300 circularConceal.start() circularConceal.addListener(object : Animator.AnimatorListener { override fun onAnimationRepeat(animation: Animator?) = Unit override fun onAnimationCancel(animation: Animator?) = Unit override fun onAnimationStart(animation: Animator?) = Unit override fun onAnimationEnd(animation: Animator?) { search_open_view.visibility = View.INVISIBLE search_input_text.setText("") circularConceal.removeAllListeners() } }) } }
view_search.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout android:id="@+id/frame" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="?actionBarSize" android:background="@color/background"> <RelativeLayout android:id="@+id/search_closed_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:visibility="visible" android:background="@color/background"> <View android:id="@+id/open_search_button" android:layout_width="24dp" android:layout_height="24dp" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_marginEnd="16dp" android:background="@drawable/search" android:backgroundTint="@color/translucent"/> </RelativeLayout> <RelativeLayout android:id="@+id/search_open_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:layout_margin="4dp" android:background="@drawable/rounded_corner_background" android:visibility="invisible"> <View android:id="@+id/close_search_button" android:layout_width="24dp" android:layout_height="24dp" android:layout_alignParentStart="true" android:layout_centerVertical="true" android:layout_marginStart="16dp" android:background="@drawable/ic_round_close_24" android:backgroundTint="@color/translucent"/> <EditText android:id="@+id/search_input_text" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_toStartOf="@id/execute_search_button" android:layout_toEndOf="@id/close_search_button" /> <!-- TODO add add button --> <View android:id="@+id/execute_search_button" android:layout_width="24dp" android:layout_height="24dp" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_marginEnd="16dp" android:background="@drawable/add" android:backgroundTint="@color/translucent"/> </RelativeLayout> </FrameLayout>
activity_location.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/background" android:padding="30dp" tools:context=".activities.LocationsActivity"> <!-- Toolbar --> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/cl_ToolBar" android:layout_width="match_parent" android:layout_height="?actionBarSize" android:elevation="1dp" android:layout_marginTop="20dp" app:layout_constraintTop_toTopOf="parent"> <LinearLayout android:id="@+id/ll_back" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/backicon" /> <TextView android:id="@+id/tv_CurrentLocation" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="5dp" android:text="Select City" android:textColor="@color/translucent" android:textSize="18sp" android:fontFamily="@font/ubuntu_condensed_regular" /> </LinearLayout> <ImageButton android:id="@+id/ib_currentLocations" android:layout_width="24dp" android:layout_height="24dp" android:layout_marginEnd="60dp" android:background="@drawable/current_location" android:backgroundTint="@color/translucent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> <!-- Search View --> <com.virusnetic.meather.views.SearchView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" app:layout_constraintTop_toTopOf="parent"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_locations" android:layout_width="match_parent" android:layout_height="wrap_content" tools:listitem="@layout/location_list_item" tools:itemCount="3" app:layout_constraintTop_toBottomOf="@id/cl_ToolBar" /> </androidx.constraintlayout.widget.ConstraintLayout>
解决方案
问题原因
自定义SearchView内部调用findViewById(R.id.cl_ToolBar)时,找不到目标控件。因为cl_ToolBar属于父布局activity_location.xml,并非SearchView自身布局的一部分,所以返回null,触发空指针异常。
解决步骤
- 在SearchView中添加回调接口
用于通知外部组件切换Toolbar的可见性:
class SearchView( context: Context, attrs: AttributeSet ) : FrameLayout(context, attrs) { // 定义回调接口 interface OnSearchStateChangeListener { fun onSearchOpened() fun onSearchClosed() } private var listener: OnSearchStateChangeListener? = null // 设置回调的方法 fun setOnSearchStateChangeListener(listener: OnSearchStateChangeListener) { this.listener = listener } init { LayoutInflater.from(context) .inflate(R.layout.view_search, this, true) findViewById<View>(R.id.open_search_button).setOnClickListener { listener?.onSearchOpened() // 调用回调通知外部 openSearch() } findViewById<View>(R.id.close_search_button).setOnClickListener { closeSearch() listener?.onSearchClosed() // 调用回调通知外部 } } // ... 原有openSearch、closeSearch方法保持不变 }
- 在LocationsActivity中实现回调
在Activity中获取cl_ToolBar,并通过回调控制它的可见性:
class LocationsActivity : AppCompatActivity(), SearchView.OnSearchStateChangeListener { private lateinit var clToolBar: ConstraintLayout private lateinit var searchView: SearchView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_location) clToolBar = findViewById(R.id.cl_ToolBar) // 注意给布局中的SearchView添加id,比如android:id="@+id/search_view" searchView = findViewById(R.id.search_view) searchView.setOnSearchStateChangeListener(this) } override fun onSearchOpened() { clToolBar.visibility = View.INVISIBLE } override fun onSearchClosed() { clToolBar.visibility = View.VISIBLE } }
- 给布局中的SearchView添加id
修改activity_location.xml中的SearchView标签,添加id属性:
<com.virusnetic.meather.views.SearchView android:id="@+id/search_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" app:layout_constraintTop_toTopOf="parent"/>
内容的提问来源于stack exchange,提问作者Sahil Kumar
相关产品推荐
相关产品推荐

