包含自定义视图的Android UI测试片段
我需要测试一个包含自定义视图的片段。我刚开始编写UI测试,但遇到了一个错误:包含自定义视图的Android UI测试片段,android,testing,instrumentation,Android,Testing,Instrumentation,我需要测试一个包含自定义视图的片段。我刚开始编写UI测试,但遇到了一个错误: android.view.InflateException: Binary XML file line #36 in *****.staging.debug:layout/fragment_form: Binary XML file line #36 in *****.staging.debug:layout/fragment_form: Error inflating class *****.util.view.Br
android.view.InflateException: Binary XML file line #36 in *****.staging.debug:layout/fragment_form: Binary XML file line #36 in *****.staging.debug:layout/fragment_form: Error inflating class *****.util.view.BrandedEditText
Caused by: android.view.InflateException: Binary XML file line #36 in *****.staging.debug:layout/fragmentn_form: Error inflating class *****.util.view.BrandedEditText
Caused by: java.lang.NoSuchMethodException: *****.util.view.BrandedEditText.<init> [class android.content.Context, interface android.util.AttributeSet]
FormFragment:
class FormFragment : BaseFragment(), Injectable {
private val loadingDialog by lazy { LoadingDialog(requireContext()) }
@Inject
lateinit var viewModelFactory: DaggerViewModelFactory<FormViewModel>
private val viewModel: FormViewModel by lazy {
ViewModelProvider(
this,
viewModelFactory
).get(FormViewModel::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val binding: FragmentFormBinding = setAndBindContentView(inflater, container, R.layout.fragment_form)
binding.viewModel = viewModel
return binding.root
}
...
类FormFragment:BaseFragment(),可注入{
通过惰性{loadingDialog(requireContext())}加载私有val loadingDialog
@注入
lateinit变量viewModelFactory:DaggerViewModelFactory
private val viewModel:FormViewModel by lazy{
ViewModelProvider(
这
viewModelFactory
).get(FormViewModel::class.java)
}
覆盖创建视图(充气机:布局充气机,容器:ViewGroup?,savedInstanceState:Bundle?):视图{
val绑定:FragmentFormBinding=setAndBindContentView(充气机、容器、R.layout.fragment_表单)
binding.viewModel=viewModel
返回binding.root
}
...
基本片段:
abstract class BaseFragment : Fragment(), NavControllerOwner {
protected var binding: ViewDataBinding? = null
protected var isAlreadyBound = false
protected val mainActivity: MainActivity?
get() = activity as? MainActivity
override val navController: NavController?
get() = (activity as NavControllerOwner).navController
@Suppress("UNCHECKED_CAST")
protected fun <ViewBindingType : ViewDataBinding?> setAndBindContentView(
inflater: LayoutInflater,
container: ViewGroup?,
@LayoutRes layoutResId: Int
): ViewBindingType {
binding?.let {
isAlreadyBound = true
binding?.lifecycleOwner = viewLifecycleOwner
return it as ViewBindingType
}
binding = DataBindingUtil.inflate(inflater, layoutResId, container, false)
binding?.lifecycleOwner = viewLifecycleOwner
return binding as ViewBindingType
}
protected fun setContentView(
inflater: LayoutInflater,
container: ViewGroup?,
@LayoutRes layoutResID: Int
): View = inflater.inflate(layoutResID, container, false)
}
抽象类BaseFragment:Fragment(),NavControllerOwner{
受保护的变量绑定:ViewDataBinding?=null
受保护变量isAlreadyBound=false
受保护的val mainActivity:mainActivity?
get()=活动作为?main活动
覆盖val导航控制器:导航控制器?
get()=(作为NavControllerOwner的活动)。navController
@抑制(“未选中的_CAST”)
受保护的乐趣集和BindContentView(
充气机,
容器:视图组?,
@LayoutRes layoutResId:Int
):ViewBindingType{
捆绑?让我来{
isAlreadyBound=true
绑定?.lifecycleOwner=viewLifecycleOwner
将其作为ViewBindingType返回
}
绑定=数据绑定直到充气(充气器、布局器、容器、假)
绑定?.lifecycleOwner=viewLifecycleOwner
以ViewBindingType的形式返回绑定
}
受保护的趣味setContentView(
充气机,
容器:视图组?,
@LayoutRes layoutResID:Int
):视图=充气机。充气(layoutResID、容器、假)
}
自定义视图:
import android.content.Context
import android.os.Parcelable
import android.text.InputFilter
import android.text.InputFilter.LengthFilter
import android.text.InputType
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatEditText
import androidx.databinding.BindingAdapter
import com.google.android.material.textfield.TextInputLayout
import *****.R
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.view_branded_edit_text.view.*
/**
* Styled edit text. Use android:inputType for input type, and app:editTextTitle for the title.
*/
class BrandedEditText(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) {
val editText: AppCompatEditText
get() = brandedAppCompatEditText
val inputLayout: TextInputLayout
get() = textInputLayout
init {
View.inflate(context, R.layout.view_branded_edit_text, this)
context.theme.obtainStyledAttributes(
attrs,
R.styleable.BrandedEditText,
0, 0
).apply {
title.text = getString(R.styleable.BrandedEditText_editTextTitle)
brandedAppCompatEditText.inputType = getInt(R.styleable.BrandedEditText_android_inputType, InputType.TYPE_NULL)
brandedAppCompatEditText.filters = arrayOf<InputFilter>(
LengthFilter(getInt(R.styleable.BrandedEditText_maxLength, Int.MAX_VALUE))
)
if (getBoolean(R.styleable.BrandedEditText_passwordToggleEnabled, false)) {
inputLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
}
}
}
fun setMaxLength(maxLength: Int) {
brandedAppCompatEditText.filters = arrayOf<InputFilter>(
LengthFilter(maxLength)
)
}
override fun onSaveInstanceState(): Parcelable? = SavedState(super.onSaveInstanceState(), editText.text.toString())
override fun onRestoreInstanceState(state: Parcelable?) {
super.onRestoreInstanceState(state)
editText.setText((state as? SavedState)?.text)
}
companion object {
@JvmStatic
@BindingAdapter("app:errorText")
fun setErrorText(view: BrandedEditText, @StringRes textId: Int) {
if (textId == 0x0) return
view.inputLayout.error = view.context.getString(textId)
}
}
@Parcelize
data class SavedState(
val superStateValue: Parcelable?,
val text: String?
) : BaseSavedState(superStateValue)
}
导入android.content.Context
导入android.os.Parcelable
导入android.text.InputFilter
导入android.text.InputFilter.LengthFilter
导入android.text.InputType
导入android.util.AttributeSet
导入android.view.view
导入android.widget.FrameLayout
导入androidx.annotation.StringRes
导入androidx.appcompat.widget.AppCompatEditText
导入androidx.databinding.BindingAdapter
导入com.google.android.material.textfield.TextInputLayout
进口******.R
导入kotlinx.android.Parcelize.Parcelize
导入kotlinx.android.synthetic.main.view\u branded\u edit\u text.view*
/**
*样式化编辑文本。输入类型使用android:inputType,标题使用app:editTextTitle。
*/
类BrandedEditText(上下文:上下文,属性集):FrameLayout(上下文,属性集){
val editText:AppCompativeEditText
get()=BrandedAppCompativeText
val inputLayout:TextInputLayout
get()=textInputLayout
初始化{
查看。充气(上下文,右布局。查看\u品牌\u编辑\u文本,此)
context.theme.obtainStyledAttributes(
属性,
R.styleable.BrandedEditText,
0, 0
).申请{
title.text=getString(R.styleable.BrandedEditText\u editTextTitle)
BrandedAppCompativeText.inputType=getInt(R.styleable.BrandedEditText\u android\u inputType,inputType.TYPE\u NULL)
BrandedAppCompativeText.filters=arrayOf(
LengthFilter(getInt(R.styleable.BrandedEditText\u maxLength,Int.MAX\u VALUE))
)
if(getBoolean(R.styleable.BrandedEditText\u passwordToggleEnabled,false)){
inputLayout.endIconMode=TextInputLayout.END\u图标\u密码\u切换
}
}
}
fun setMaxLength(maxLength:Int){
BrandedAppCompativeText.filters=arrayOf(
长度过滤器(最大长度)
)
}
重写onSaveInstanceState():Parcelable?=SavedState(super.onSaveInstanceState(),editText.text.toString())
覆盖恢复安装状态(状态:可包裹?){
super.onRestoreInstanceState(状态)
editText.setText((状态为?SavedState)?.text)
}
伴星{
@JvmStatic
@BindingAdapter(“应用程序:errorText”)
fun setErrorText(视图:BrandedEditText,@StringRes textId:Int){
if(textId==0x0)返回
view.inputLayout.error=view.context.getString(textId)
}
}
@包裹
数据类存储状态(
val超级状态值:可包裹?,
val文本:字符串?
):BaseSavedState(超级状态值)
}
我尝试在src/androidTest目录中创建一个具有相同签名的类,这很有帮助。但是,为什么会发生此错误仍然是一个谜。此外,我认为每次发生类似错误时复制相同的代码并不完全正确
import android.content.Context
import android.os.Parcelable
import android.text.InputFilter
import android.text.InputFilter.LengthFilter
import android.text.InputType
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatEditText
import androidx.databinding.BindingAdapter
import com.google.android.material.textfield.TextInputLayout
import *****.R
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.view_branded_edit_text.view.*
/**
* Styled edit text. Use android:inputType for input type, and app:editTextTitle for the title.
*/
class BrandedEditText(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) {
val editText: AppCompatEditText
get() = brandedAppCompatEditText
val inputLayout: TextInputLayout
get() = textInputLayout
init {
View.inflate(context, R.layout.view_branded_edit_text, this)
context.theme.obtainStyledAttributes(
attrs,
R.styleable.BrandedEditText,
0, 0
).apply {
title.text = getString(R.styleable.BrandedEditText_editTextTitle)
brandedAppCompatEditText.inputType = getInt(R.styleable.BrandedEditText_android_inputType, InputType.TYPE_NULL)
brandedAppCompatEditText.filters = arrayOf<InputFilter>(
LengthFilter(getInt(R.styleable.BrandedEditText_maxLength, Int.MAX_VALUE))
)
if (getBoolean(R.styleable.BrandedEditText_passwordToggleEnabled, false)) {
inputLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
}
}
}
fun setMaxLength(maxLength: Int) {
brandedAppCompatEditText.filters = arrayOf<InputFilter>(
LengthFilter(maxLength)
)
}
override fun onSaveInstanceState(): Parcelable? = SavedState(super.onSaveInstanceState(), editText.text.toString())
override fun onRestoreInstanceState(state: Parcelable?) {
super.onRestoreInstanceState(state)
editText.setText((state as? SavedState)?.text)
}
companion object {
@JvmStatic
@BindingAdapter("app:errorText")
fun setErrorText(view: BrandedEditText, @StringRes textId: Int) {
if (textId == 0x0) return
view.inputLayout.error = view.context.getString(textId)
}
}
@Parcelize
data class SavedState(
val superStateValue: Parcelable?,
val text: String?
) : BaseSavedState(superStateValue)
}