ViewPager Focus Issue

前端 未结 2 772
攒了一身酷
攒了一身酷 2020-12-12 03:58

I have a ViewPager that holds 4 fragments/panels, each half-width of a landscaped screen.

 _______________ _______________ _______________ _______________
|1         


        
相关标签:
2条回答
  • 2020-12-12 04:15

    The solution I found was not trival, but it suffices, and isn't too much overhead.

    Because the default behavior of Android is to give a touch/click event to the child-most view, which in this case where EditText's, I had to subclass the EditText's and override their OnTouchEvent members.

    Delegation in this case would not suffice

    myEditText.Touch += EditTextTouchEventHandler;
    

    since I still wanted the default behavior that the EditText (built-in) touch event (which does a few nice things like position the cursor where the user touched, and not simply at the beginning of the text box).

    In the OnTouchEvent, the view-pager that held the fragment that this EditText was residing in was notified of this click event, and the EditText focusing is handled manually (so when any refresh happenes - ie, keyboard popping up - and therefore a RequestFocus fires, the last clicked-in EditText will not loose focus (as excpected). Communication was done through the Android recommended parent-listener pattern for fragment "callbacks", ie

    Also, the ViewPager's RequestChildFocus method must be overridden so the event can be intercepted and eaten. In its place, a secondary RequestChildFocus2 method is manually called, which in turn calls the base class RequestChildFocus.

    The Activity that contained the ViewPager defined and implement the interface

    public interface IFragmentToViewPagerEvent
    {
        void ParcelRecordFieldClickEvent(Fragment child, View focused);
    }
    

    And each fragment would hold a reference to the activity, casted to this interface, so the fragment can communicate to the ViewPager via the inteface methods (another interface can declared that the Fragments implement so the ViewPager can call the fragments - then both directions of communication are covered).

    private ParcelView.IFragmentToViewPagerEvent _fragmentToViewPagerEvent;
    
    public override void OnAttach(Activity activity)
    {
        base.OnAttach(activity);
    
        _fragmentToViewPagerEvent = (ParcelView.IFragmentToViewPagerEvent) activity;
    }
    

    Heres what the subclass looks like:

    RecordNoteBoxInput was a custom class created to be an EditText that was to be placed in a tab, that expanded to fill the tab that contains it, with a uniform width boarder all around.

    public class RecordNoteBoxInput : EditText
    {
        // sometime u need this, other times you don't - mono's missing nuts that android don't
        //public RecordNoteBoxInput(IntPtr jRef, JniHandleOwnership handle) : base(jRef, handle) { }
    
        public RecordNoteBoxInput(Context context) : base(context) { }
        public RecordNoteBoxInput(Context context, IAttributeSet attributes) : base(context, attributes) { }
        public RecordNoteBoxInput(Context context, IAttributeSet attributes, int defStyle) : base(context, attributes, defStyle) { }
    }
    
    public class RecordNoteBox : LinearLayout
    {
        protected Context _context;
        protected LayoutInflater _inflater;
    
        protected RecordNoteBoxInput _inputFiled;
    
        public RecordNoteBoxInput Input
        {
            get { return _inputFiled; }
            set { _inputFiled = value; }
        }
    
        protected virtual RecordNoteBoxInput InstantiateInput()
        {
            return (RecordNoteBoxInput)_inflater.Inflate(Resource.Layout.RecordNoteBoxInput, this, false);
        }
    
        protected void Init(Context context)
        {
            _context = context;
            _inflater = LayoutInflater.From(context);
            _inputFiled = InstantiateInput(); 
    
            this.AddView(_inputFiled);
        }
    
        // sometime u need this, other times you don't - mono's missing nuts that android don't
        //public RecordNoteBox(IntPtr jRef, JniHandleOwnership handle) : base(jRef, handle) { }
    
        public RecordNoteBox(Context context) : base(context) { Init(context); }
        public RecordNoteBox(Context context, IAttributeSet attributes) : base(context, attributes) { Init(context); }
        public RecordNoteBox(Context context, IAttributeSet attributes, int defStyle) : base(context, attributes, defStyle) { Init(context); }
    
    }
    

    PagedFragmentRecordNoteBox was created so that the OnTouchEvent of the EditText composed inside RecordNoteBox could be overriden, allowing a chance to signal to the containing ViewPager which EditText no has focus. SetCurrentItem() cannot be used, since I'm using half-screen ViewPager pages (returning 0.5f for PageWidth in ViewPagerAdapter), any page that is currently on the right side will end up being automatically shifted to the left upon a call to SetCurrentItem, since this is the defualt behavior of a ViewPager, and I wasn't willing to rewrite this function. Also, there is an internal/private version of this function that I cannot control without rewriting the entire ViewPager class.

    public class PagedFragmentRecordNoteBox : RecordNoteBox
    {
        public Fragment ParentFragment { get; set; }
        public RecordView.IFragmentToViewPagerEvent PagerListener { get; set; }
    
        private PagedFragmentRecordNoteBoxInput _pagedFragmentFieldInput;
    
        // sometime u need this, other times you don't - mono's missing nuts that android don't
        //public PagedFragmentRecordNoteBox(IntPtr jRef, JniHandleOwnership handle) : base(jRef, handle) { }
    
        public PagedFragmentRecordNoteBox(Context context) : base(context) { }
        public PagedFragmentRecordNoteBox(Context context, IAttributeSet attributes) : base(context, attributes) { }
        public PagedFragmentRecordNoteBox(Context context, IAttributeSet attributes, int defStyle) : base(context, attributes, defStyle) { }
    
        protected override RecordNoteBoxInput InstantiateInput()
        {
            // Since I was getting inflation exception when the layout file 
            // PagedFragmentRecordRecordNoteBoxInput.axml had only a single, not wrapped in any ViewGroup, I had to 
            // wrap it up in a linear layout (whatever). Since this view will be added somewhere else, it needs to 
            // be removed from the wrapper (hence the call to RemoveAllViews - comment it out to see what happens).
    
            //LayoutInflater inflater = (LayoutInflater)_context.ApplicationContext.GetSystemService(Context.LayoutInflaterService);
            View v = _inflater.Inflate(Resource.Layout.PagedFragmentRecordRecordNoteBoxInput, null);
    
            _pagedFragmentFieldInput = ((ViewGroup)v).FindViewById<PagedFragmentRecordNoteBoxInput>(Resource.Id.fi_record_note_box_input);
    
            ((ViewGroup)v).RemoveAllViews();
    
            _pagedFragmentFieldInput.OuterClass = this;
    
            return (RecordNoteBoxInput)_pagedFragmentFieldInput;
        }
    
        protected class PagedFragmentRecordNoteBoxInput : RecordNoteBoxInput
        {
            public PagedFragmentRecordNoteBox OuterClass { get; set; }
    
            private Context _context { get; set; }
    
            // sometime u need this, other times you don't - mono's missing nuts that android don't
            //public PagedFragmentRecordNoteBoxInput(IntPtr jRef, JniHandleOwnership handle) : base(jRef, handle) { }
    
            public PagedFragmentRecordNoteBoxInput(Context context) : base(context) { _context = context; }
            public PagedFragmentRecordNoteBoxInput(Context context, IAttributeSet attributes) : base(context, attributes) { _context = context; }
            public PagedFragmentRecordNoteBoxInput(Context context, IAttributeSet attributes, int defStyle) : base(context, attributes, defStyle) { _context = context; }
    
            public override bool OnTouchEvent(MotionEvent e)
            {
                OuterClass.PagerListener.ParcelRecordFieldClickEvent(OuterClass.ParentFragment, this);
    
                return base.OnTouchEvent(e);
            }
        }
    }
    

    Then, in OnCreateView of on of the fragments that has an EditText, I would set up the EditText (or, its subclass) like

    _thisView = (ViewGroup) _inflater.Inflate(Resource.Layout.Record, _container, false);
    
    PagedFragmentRecordNoteBox userNotes = _thisView.FindViewById<PagedFragmentRecordNoteBox>(Resource.Id.ll_record_note_box);
    
    PagedFragmentRecordNoteBox userNotes.ParentFragment = this;
    PagedFragmentRecordNoteBox userNotes.PagerListener = _fragmentToViewPagerEvent;
    

    The main activity that hosted the ViewPager looks like

    public class ParcelView : Activity
    {
        protected ViewPager _viewPager;
        private List<Android.App.Fragment> _fragments;
    
        public interface IFragmentToViewPagerEvent
        {
            void ParcelRecordFieldClickEvent(Fragment child, View focused);
        }
    
        void IFragmentToViewPagerEvent.ParcelRecordFieldClickEvent(Fragment child, View focused)
        {
            _viewPager.RequestChildFocus2(null, focused);
        }   
    }
    

    One last piece that is required is to override the ViewPager's RequestChildFocus() method and to eat it the request:

    public class ViewPager2 : ViewPager
    {
        private view _clearFocused;
    
        public override void RequestChildFocus(View child, View focused)
        {
            //base.RequestChildFocus(child, focused);
        }
    
        public void RequestChildFocus2(View child, View focused)
        {            
            if( _clearFocused != null )
            {
                _clearFocused.ClearFocus();
            }
    
            _clearFocused = focused;
    
            base.RequestChildFocus(child, focused);
        }
    
    }
    

    where the layout files are defined as follows

    Fragment.axml (one of the fragments main layouts):

      ...
      <TabHost
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/tabhost"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    
        <LinearLayout
          xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/ll_record_detail_tab"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical"
          android:background="#FF200000">
    
          <FieldInspection.Droid.Views.MyTabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
    
          <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">
    
            ...
    
            <include layout="@layout/RecordNoteBox"/>
    
            ...
    
          </FrameLayout>
    
        </LinearLayout>
    
      </TabHost>
      ...
    

    RecordNoteBox.axml

    <?xml version="1.0" encoding="utf-8"?>
    <!--fieldinspection.droid.views.custom.RecordNoteBox-->
    <fieldinspection.droid.views.custom.PagedFragmentRecordNoteBox
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/ll_record_note_box"
      android:orientation="vertical"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#FF555555">
    
    </fieldinspection.droid.views.custom.PagedFragmentRecordNoteBox>
    

    RecordNotBoxInput.axml

    <?xml version="1.0" encoding="utf-8"?>
    <fieldinspection.droid.views.custom.FieldInput
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/fi_record_note_box_input"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#FFFFFFFF"
      android:gravity="top"
      android:textSize="20sp"
      android:textColor="#FF000000"
      android:layout_margin="20dip"/> 
    

    PagedFragmentRecordNoteBoxInput.axml - Notice its wrapped in a LinearLayout. This was to avoid inflation exception. Not sure yet why some views need to be wrapped in a ViewGroup in order to be inflated, but they do (whats even weirder is that ViewGroup is a subclass of View, so I can figure it out).

    <?xml version="1.0" encoding="utf-8"?>
    
    <LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/ll_record_note_box_input_container"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content">
    
      <FieldInspection.Droid.Views.Custom.PagedFragmentRecordNoteBox.PagedFragmentRecordNoteBoxInput
        android:id="@+id/fi_record_note_box_input"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#FFFFFFFF"
        android:gravity="top"
        android:textSize="20sp"
        android:textColor="#FF000000"
        android:layout_margin="20dip"/>
    
      </LinearLayout>
    
    0 讨论(0)
  • 2020-12-12 04:22

    My answer lies within this stack trace, which is from the OnFocusChange() override of the EditText subclass EditText2 mentioned above (good times tracing this):

    Note: This is just for archival purposes only, to potentially help any future readers with this issue. I will not be accepting this as my answer and am still open for help (Thanks again).


    dalvik.system.VMStack.getThreadStackTrace(Native Method) java.lang.Thread.getStackTrace(Thread.java:591) appname.droid.views.EditText2.n_onFocusChanged(Native Method) appname.droid.views.EditText2.onFocusChanged(EditText2.java:53) android.view.View.handleFocusGainInternal(View.java:3680) android.view.View.requestFocus(View.java:5373) android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:2154) android.view.ViewGroup.requestFocus(ViewGroup.java:2110) android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:2154) android.view.ViewGroup.requestFocus(ViewGroup.java:2110) android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:2154) android.view.ViewGroup.requestFocus(ViewGroup.java:2110) android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:2154) android.view.ViewGroup.requestFocus(ViewGroup.java:2113) android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:2154) android.view.ViewGroup.requestFocus(ViewGroup.java:2110) android.view.View.requestFocus(View.java:5323) android.support.v4.view.ViewPager.populate(ViewPager.java:1051) android.support.v4.view.ViewPager.populate(ViewPager.java:881) android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1366) android.view.View.measure(View.java:12728) android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4698) android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1369) android.widget.LinearLayout.measureVertical(LinearLayout.java:660) android.widget.LinearLayout.onMeasure(LinearLayout.java:553) android.view.View.measure(View.java:12728) android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4698) android.widget.FrameLayout.onMeasure(FrameLayout.java:293) android.view.View.measure(View.java:12728) android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4698) android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1369) android.widget.LinearLayout.measureVertical(LinearLayout.java:660) android.widget.LinearLayout.onMeasure(LinearLayout.java:553) android.view.View.measure(View.java:12728) android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4698) android.widget.FrameLayout.onMeasure(FrameLayout.java:293) com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2092) android.view.View.measure(View.java:12728) android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1064) android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2442) android.os.Handler.dispatchMessage(Handler.java:99) android.os.Looper.loop(Looper.java:137) android.app.ActivityThread.main(ActivityThread.java:4424) java.lang.reflect.Method.invokeNative(Native Method) java.lang.reflect.Method.invoke(Method.java:511) com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) dalvik.system.NativeStart.main(Native Method)

    public class ViewPager extends ViewGroup 
    {
        ...
    
        void populate 
        {
            ...
    
            if (hasFocus()) {
                View currentFocused = findFocus();
                ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
                if (ii == null || ii.position != mCurItem) {
                    for (int i=0; i<getChildCount(); i++) {
                    View child = getChildAt(i);
                    ii = infoForChild(child);
                    if (ii != null && ii.position == mCurItem) {
                        if (child.requestFocus(FOCUS_FORWARD)) {
                            break;
                        }
                    }
                }
            }
    
            ...
        }
    
        ...
    }
    
    0 讨论(0)
提交回复
热议问题