可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Let's say I have this layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageButton
android:id="@+id/add_dep_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:src="@android:drawable/ic_input_add" />
<EditText
android:id="@+id/add_dep_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/add_dep_btn"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignTop="@id/add_dep_btn"
android:layout_marginLeft="5dp"
android:layout_marginStart="5dp"
android:layout_toLeftOf="@id/add_dep_btn"
android:layout_toStartOf="@id/add_dep_btn" />
<android.support.v7.widget.RecyclerView
android:id="@+id/dep_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/add_dep_btn" />
<TextView
android:id="@+id/empty_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/add_dep_text"
android:layout_margin="20dp"
android:gravity="center"
android:text="@string/no_dep"
android:textSize="22sp" />
</RelativeLayout>
And I use it in a DialogFragment:
class DepartmentChoiceDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
builder.setTitle(R.string.choose_or_create_dep)
.setView(R.layout.department_chooser_dialog)
.setNegativeButton(android.R.string.cancel, { d, i ->
d.cancel()
})
return builder.create()
}
}
if I refer to the widget using synthetic:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
dep_list.layoutManager = LinearLayoutManager(activity)
dep_list.itemAnimator = DefaultItemAnimator()
dep_list.setHasFixedSize(true)
}
I got this error at runtime:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.View.findViewById(int)' on a null object reference
at MyDialog._$_findCachedViewById(DepartmentChoiceDialog.kt:0)
I don't understand how to use synthetic in DialogFragment case. It works fine in Fragment and Activity.
回答1:
I found a way that works for custom dialogs.
class ServerPickerDialogFragment: AppCompatDialogFragment()
{
// Save your custom view at the class level
lateinit var customView: View;
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View?
{
// Simply return the already inflated custom view
return customView
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Inflate your view here
customView = context!!.layoutInflater.inflate(R.layout.dialog_server_picker, null)
// Create Alert Dialog with your custom view
return AlertDialog.Builder(context!!)
.setTitle(R.string.server_picker_dialog_title)
.setView(customView)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
super.onViewCreated(view, savedInstanceState)
// Perform remaining operations here. No null issues.
rbgSelectType.setOnCheckedChangeListener({ _, checkedId ->
if(checkedId == R.id.rbSelectFromList) {
// XYZ
} else {
// ABC
}
})
}
}
回答2:
It looks like this isn't supported by default yet, but I've found the easiest way to do it to be like this. In a base dialog class:
protected abstract val containerView: View
override fun getView() = containerView
In a subclass:
override val containerView by unsafeLazy {
View.inflate(context, R.layout.dialog_team_details, null) as ViewGroup
}
Then you can use the synthetic views as you normally would and use the containerView
as the view for your dialog.
回答3:
Change to onCreateView implementation
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.department_chooser_dialog, container, false)
}
and use a custom title(TextView) and cancel(Button) in the department_chooser_dialog
onActivityCreated will run after onCreateView and will be just fine.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
dep_list.layoutManager = LinearLayoutManager(activity)
dep_list.itemAnimator = DefaultItemAnimator()
dep_list.setHasFixedSize(true)
}
回答4:
So I'm not sure if this has been solved... I just came across this. If you have a custom Dialog view make a class that extends DialogFragment and use the "dialog" object to import views in the layout. I'm using Android Studio 3.1.3
and Kotlin version 1.2.41
at the time of writing.
import kotlinx.android.synthetic.main.your_custom_layout.*
class SelectCountryBottomSheet : BottomSheetDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
dialog.setContentView(R.layout.your_custom_layout)
dialog.some_custom_close_button.setOnClickListener { dismiss() }
return dialog
}
}
回答5:
Move your code from onActivityCreated
to onViewCreated
method.
Like this:
import kotlinx.android.synthetic.main.department_chooser_dialog.dep_list
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dep_list.apply {
layoutManager = LinearLayoutManager(activity)
itemAnimator = DefaultItemAnimator()
setHasFixedSize(true)
}
}
I actually didn't look deeper into generated code and maybe there is a bug.
回答6:
Previous answer will not work, because onViewCreated is not called when you use onCreateDialog. You should first import kotlinx...department_chooser_dialog.view.dep_list, an then use it as follows:
import kotlinx.android.synthetic.main.department_chooser_dialog.view.dep_list
...
class DepartmentChoiceDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
val dialog = inflater.inflate(R.layout.department_chooser_dialog, null)
dialog.dep_list.layoutManager = LinearLayoutManager(activity)
dialog.dep_list.itemAnimator = DefaultItemAnimator()
dialog.dep_list.setHasFixedSize(true)
builder.setTitle(R.string.choose_or_create_dep)
.setView(dialog)
...
回答7:
Because the default view's value from fragment(kotlin generate method _$_findCachedViewById), but if we create View from dialog, lead to fragment view is null, so we can't directly use default xxx , but we can use dialog.xxx replace default xxx
回答8:
The kotlin code in a Fragment like this:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.your_layout, container, false).apply {
mContentView = this
button1.setOnClickListener {
//do something
}
}
}
After decompile the bytecode, you can see the implementation of synthetic properties:
((Button)this._$_findCachedViewById(id.button1))
and the _$_findCachedViewById
method :
public View _$_findCachedViewById(int var1) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(var1);
if (var2 == null) {
View var10000 = this.getView();
if (var10000 == null) {
return null;
}
var2 = var10000.findViewById(var1);
this._$_findViewCache.put(var1, var2);
}
return var2;
}
so the magic is just the this.getView()
. The Fragment.mView
property is assigned after Fragment.onCreateView(inflater, container, savedInstanceState)
, if you use Kotlin Synthetic Properties in onCreateView() method, there will be a NPE. Code from FragmentManager.moveToState()
:
case Fragment.CREATED:
...
f.mView = f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container,
f.mSavedFragmentState);
...
To fix the NPE, make sure getView
method return a non-null view.
private var mContentView: View? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.your_layout, container, false).apply {
mContentView = this
}
}
override fun getView(): View? {
return mContentView
}
and at the onDestroyView()
lifecycle callback, set mContentView
to null.
override fun onDestroyView() {
super.onDestroyView()
mView = null
}
回答9:
The views are accessible via the view that you inflate in onCreateDialog
. So, if you save the view in a variable (rootView
) you can access the views from any method inside of YourDialogFragment
.
// ...
import kotlinx.android.synthetic.main.your_layout.view.*
class YourDialogFragment : DialogFragment() {
private lateinit var rootView: View
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
rootView = activity.layoutInflater.inflate(R.layout.your_layout, null as ViewGroup?)
rootView.someTextView.text = "Hello" // works
}
}