Problem with inflating custom ViewGroup.
Unable to start activity
Binary XML file line #14: Binary XML file line #14: Error inflating class com.example.interactiveplatform.view.GraphLayout.
I searched about this problem and there is something wrong with constructors.
<LinearLayout
android:id="#+id/graph"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".MainActivity">
<com.example.interactiveplatform.view.GraphLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.example.interactiveplatform.view.GraphLayout>
</LinearLayout>
class GraphLayout(context: Context, attrs: AttributeSet, var chapterAdapter: ChapterAdapter) : ViewGroup(context, attrs) {
private val detector: GestureDetector
private val mMoveAndScaleHandler: MoveAndScaleHandler
private val myListener: GestureDetector.SimpleOnGestureListener
private var spacing = 0
init {
myListener = object : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean {
return true
}
}
detector = GestureDetector(context, myListener)
mMoveAndScaleHandler = MoveAndScaleHandler(context, this, chapterAdapter)
}
override fun onLayout(p0: Boolean, p1: Int, p2: Int, p3: Int, p4: Int) {
for(i in 0 until childCount){
val v = getChildAt(i)
v.layout(v.left, v.top, v.right, v.bottom)
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
detector.onTouchEvent(event)
invalidate()
return mMoveAndScaleHandler.onTouchEvent(event)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(400 * chapterAdapter.numberChapters,chapterAdapter.heightScreen)
}
}
When you instantiate your custom view via XML you cannot pass custom constructor parameters like chapterAdapter. Also, you have to support all Android's view constructors. For example, you can do it like this:
class GraphLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr)
Related
In Android, I have two class files, MainActivity and CanV.
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
CanV.kt, This is a View class.
class CanV(context: Context, attributeSet: AttributeSet): View(context){ ... }
CanV view in activity_main.xml
<com.app.app_name.CanV
android:id="#+id/cans"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0">
</com.app.app_name.CanV>
Now I want to create CanV class object in MainActivity.
I came to try:
val c: CanV = CanV(this, ?)
But, I don't know the AttributeSet parameter value. How do I create and pass the AttributeSet of CanV view?
If you want to be able to create instances of CanV from both XML and Java/Kotlin, you should provide two constructors:
class CanV : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
...
}
The first can be used from Kotlin (val v: CanV = CanV(this)), and the second is used automatically when inflating from XML.
My screen consists of a ConstraintLayout with two children. The one child is a RecyclerView which takes up the entire screen. The other child is another ConstraintLayout that contains a TextView (the screen is actually more complex than this, but I'm reducing it to something here to make it easier to comprehend). The child ConstraintLayout is positioned over the RecyclerView and scrolls up whenever you scroll the RecyclerView. This is done using a scroll listener on the RecyclerView and re-adjusting the Y position of the child ConstraintLayout. The child ConstraintLayout needs to be clickable.
The problem I am having is that if you try scrolling the RecyclerView by first touching down on the child ConstraintLayout, the scrolling will not work. The click event handler for the child ConstraintLayout will work, however.
From my understanding of how Android handles touch events, if a view returns false in its onTouchEvent handler, that view will not get notified of any further motion events. So theoretically, if my child ConstraintLayout returns false in its onTouchEvent handler, the RecyclerView should still get the scroll motion events.
Here is my layout xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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:fitsSystemWindows="true"
tools:background="#55338855">
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="#+id/dashboardConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layoutDescription="#xml/dashboard_initial_animation_start"
app:showPaths="false">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/contentRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipChildren="false"
android:fitsSystemWindows="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/guideWelcome"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="1.0" />
<ReservationCard
android:id="#+id/reservationConstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="60dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="#color/dashboard_reservation_card_background_color"
android:padding="16dp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/txtSearch">
<TextView
android:id="#+id/txtReservationHeader"
style="#style/AppTheme.Label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/dashboard_screen_reservation_card_headline"
android:textColor="#color/dashboard_reservation_card_text_color" />
</com.sinnerschrader.motelone.screens.dashboard.ReservationCard>
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And here is my custom ConstraintLayout (ReservationCard):
class ReservationCard : androidx.constraintlayout.widget.ConstraintLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val gestureDetector: GestureDetector
init {
gestureDetector = GestureDetector(GestureListener())
}
override fun onTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action == MotionEvent.ACTION_DOWN) {
gestureDetector.onTouchEvent(ev)
return true
} else if (ev?.action == MotionEvent.ACTION_UP) {
gestureDetector.onTouchEvent(ev)
return false
} else {
super.onTouchEvent(ev)
return false
}
}
private inner class GestureListener : GestureDetector.OnGestureListener {
override fun onFling(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
return false
}
override fun onScroll(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
return false
}
override fun onLongPress(p0: MotionEvent?) {
}
override fun onDown(p0: MotionEvent?): Boolean {
return false
}
override fun onShowPress(p0: MotionEvent?) {
}
override fun onSingleTapUp(p0: MotionEvent?): Boolean {
return false
}
}
}
I have a custom view that extends LinearLayout and inflates a Layout which contains a couple of Views.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
xmlns:android="http://schemas.android.com/apk/res/android">
<EditText
android:id="#+id/voice_edittext"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="Add answer here"
/>
<ImageButton
android:id="#+id/microphone_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_mic_black_24dp"
/>
<ImageButton
android:id="#+id/delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_cancel_black_24dp"
android:visibility="gone"
/>
</LinearLayout>
I now want to two-way bind the text value of the Edittext when I use this view, like this:
<com.sunilson.quizcreator.presentation.views.EditTextWithVoiceInput
android:id="#+id/form_question"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
app:editTextValue="#={viewModel.observableText}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
For this I created some Binding Adapters
#BindingAdapter("editTextValueAttrChanged")
fun setListener(editTextWithVoiceInput: EditTextWithVoiceInput, listener: InverseBindingListener) {
editTextWithVoiceInput.voice_edittext.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
listener.onChange()
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
}
#BindingAdapter("editTextValue")
fun setTextValue(editTextWithVoiceInput: EditTextWithVoiceInput, value: String?) {
if (value != editTextWithVoiceInput.voice_edittext.text.toString()) editTextWithVoiceInput.voice_edittext.setText(value)
}
#InverseBindingAdapter(attribute = "editTextValue")
fun getTextValue(editTextWithVoiceInput: EditTextWithVoiceInput): String? {
return editTextWithVoiceInput.voice_edittext.text.toString()
}
This is the code of the view:
class EditTextWithVoiceInput(context: Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {
init {
val inflater = context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.voice_edittext, this, true)
view.microphone_button.setOnTouchListener { p0, p1 ->
...
}
}
}
The problem now is that when the Fragment containing the view is started, I get this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.sunilson.quizcreator, PID: 12627
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.sunilson.quizcreator.presentation.views.EditTextWithVoiceInput.setTag(java.lang.Object)' on a null object reference
at com.sunilson.quizcreator.databinding.FragmentAddQuestionBinding.<init>(FragmentAddQuestionBinding.java:112)
at android.databinding.DataBinderMapperImpl.getDataBinder(DataBinderMapperImpl.java:15)
at android.databinding.DataBindingUtil.bind(DataBindingUtil.java:199)
at android.databinding.DataBindingUtil.inflate(DataBindingUtil.java:130)
at android.databinding.DataBindingUtil.inflate(DataBindingUtil.java:95)
at com.sunilson.quizcreator.presentation.SingleActivity.fragments.AddQuestionFragment.AddQuestionFragment.onCreateView(AddQuestionFragment.kt:34)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2425)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2623)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2410)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2365)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2272)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:733)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:180)
at android.app.ActivityThread.main(ActivityThread.java:6944)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:835)
What am I missing here?
Ok, so my problem was that my custom view had incorrectly implemented the constructor functions. As I inflate this view in XML, I needed the constructor where I get passed the Attributeset to pass that Attributeset to the super constructor, which I did not do. Without that, my view had no attributes and could not be found via it's ID etc.
Now I have two constructors, depending on if I inflate from XML or code:
constructor(context: Context, optional: Boolean) : super(context) {
...
}
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
...
}
I created a custom view, and tried to restore the state automatically on screen rotation (just as EditText restores current input text automatically), but when I see the log, onSaveInstanceState is not called, and only onRestoreInstanceState is called. What is wrong?
class MyView:LinearLayout
{
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context, attrs, defStyleAttr)
init
{
isSaveEnabled=true
}
override fun onSaveInstanceState(): Parcelable
{
return super.onSaveInstanceState()
Log.d("ss", "save")
}
override fun onRestoreInstanceState(state: Parcelable?)
{
super.onRestoreInstanceState(state)
Log.d("ss", "restore")
}
}
Activity layout:
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/top"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.loser.mylayouttest.MyView
android:id="#+id/myView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</AbsoluteLayout>
Activity:
class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
The reason why you can't see Log.d("ss", "save") being called is simply that the code line is invoked after the return statement. The onSaveInstanceState() is actually called. To see the log move Log.d("ss", "save") above return super.onSaveInstanceState().
I have SimpleIME.kt class in order to implement simple InputMethodEditor and do my custom keyboard on android:
class SimpleIME : InputMethodService(), OnKeyboardActionListener {
private var kv: CustomKeyboardView? = null
private var keyboard: Keyboard? = null
companion object{
const val TAG :String = "myLogs"
}
private var caps = false
override fun onCreateInputView(): View? {
kv = layoutInflater.inflate(R.layout.keyboard, null) as CustomKeyboardView
keyboard = Keyboard(this, R.xml.qwerty)
kv!!.keyboard = keyboard
kv!!.setOnKeyboardActionListener(this)
Log.d(TAG,"onCreateInputView")
return kv
}
...
}
and my CustomKeuboardView class as below:
class CustomKeyboardView : KeyboardView {
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)
override fun onKeyMultiple(keyCode: Int, repeatCount: Int, event: KeyEvent?): Boolean {
return super.onKeyMultiple(keyCode, repeatCount, event)
}
override fun onTouchEvent(me: MotionEvent?): Boolean {
Log.d(TAG,"onTouchEvent "+me.toString())
return super.onTouchEvent(me)
}
}
it throws classCastException as below:
java.lang.ClassCastException: android.inputmethodservice.KeyboardView cannot be cast to com.support.mukhtar.simplekeyboard.CustomKeyboardView
at com.support.mukhtar.simplekeyboard.SimpleIME.onCreateInputView(SimpleIME.kt:30)
at android.inputmethodservice.InputMethodService.updateInputViewShown(InputMethodService.java:1248)
at android.inputmethodservice.InputMethodService.showWindowInner(InputMethodService.java:1669)
at android.inputmethodservice.InputMethodService.showWindow(InputMethodService.java:1636)
at android.inputmethodservice.InputMethodService$InputMethodImpl.showSoftInput(InputMethodService.java:497)
at android.inputmethodservice.IInputMethodWrapper.executeMessage(IInputMethodWrapper.java:202)
at com.android.internal.os.HandlerCaller$MyHandler.handleMessage(HandlerCaller.java:40)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5426)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
at dalvik.system.NativeStart.main(Native Method)
if I implement SimpleIME.kt class as follow:
class SimpleIME : InputMethodService(), OnKeyboardActionListener {
private var kv: KeyboardView? = null
private var keyboard: Keyboard? = null
companion object{
const val TAG :String = "myLogs"
}
private var caps = false
override fun onCreateInputView(): View? {
kv = layoutInflater.inflate(R.layout.keyboard, null) as KeyboardView
keyboard = Keyboard(this, R.xml.qwerty)
kv!!.keyboard = keyboard
kv!!.setOnKeyboardActionListener(this)
Log.d(TAG,"onCreateInputView")
return kv
}
...
}
it works fine but I need implement onTouchEvent in KeyboardView in order to handle multiple clicks so I think it is necessary to implement CustomKeyboardView Class;
There is an example of CustomKeyboardView written in java: https://github.com/blackcj/AndroidCustomKeyboard.git
Please help
my question does not same as this: java.lang.ClassCastException: android.inputmethodservice.KeyboardView cannot be cast to android.view.ViewGroup
because there is a problem with layout.xml but i do not have any problem with it because it works if i take as KeyboardView as shown before
my layout.keyboard.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<android.inputmethodservice.KeyboardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/keyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#3Aad1a"
android:keyPreviewLayout ="#layout/preview"
/>
Just replace android.inputmethodservice.KeyboardView to CustomKeyboardView in R.layout.keyboard xml file.