Metoda przycisku fragmentu systemu Android onClick

91

Próbuję wywołać metodę w moim XML onClick (View v), ale nie działa z fragmentem. To jest błąd.

01-17 12:38:36.840: E/AndroidRuntime(4171): java.lang.IllegalStateException: 
Could not find a method insertIntoDb(View) in the activity class main.MainActivity 
for onClick handler on view class android.widget.Button with id 'btn_conferma'

Kod Java:

public void insertIntoDb(View v) {
...
} 

XML:

<Button
        android:id="@id/btn_conferma"
        style="?android:attr/borderlessButtonStyle"
        android:layout_width="0.0dip"
        android:layout_height="45dp"
        android:layout_marginLeft="2dp"
        android:layout_weight="1.0"
        android:background="@drawable/bottoni"
        android:gravity="center_horizontal|center_vertical"
        android:onClick="insertIntoDb"
        android:text="SALVA"
        android:textColor="#ffffff"
        android:textSize="16sp" />
user3160725
źródło
1
Proszę zamieścić więcej śladów stosu i odpowiedniego kodu
Emmanuel
2
Jak stwierdzono tutaj developer.android.com/guide/topics/ui/controls/… , „Działanie obsługujące układ musi następnie zaimplementować odpowiednią metodę”. W twoim przypadku zaimplementowałeś odpowiednią metodę w swoim fragmencie. Z tego powodu zgłasza wyjątek IllegalStateException, ponieważ nie może znaleźć odpowiedniej metody w działaniu. Może uda ci się zastosować sztuczkę, jak pokazano w tym poście stackoverflow.com/a/6271637/2515815
hcelaloner
Metoda insertIntoDb (View) musi znajdować się w klasie main.MainActivity, a nie w klasie Fragment.
betorcs
najlepsze rozwiązanie stackoverflow.com/questions/7570575/ ...
AndroidGeek

Odpowiedzi:

187

Twoja aktywność musi mieć

public void insertIntoDb(View v) {
...
} 

nie Fragment.

Jeśli nie chcesz, aby powyższe czynności były wykonywane. zainicjalizuj przycisk we fragmencie i ustaw odbiornik na to samo.

<Button
    android:id="@+id/btn_conferma" // + missing

Następnie

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

   View view = inflater.inflate(R.layout.fragment_rssitem_detail,
    container, false);
   Button button = (Button) view.findViewById(R.id.btn_conferma);
   button.setOnClickListener(new OnClickListener()
   {
             @Override
             public void onClick(View v)
             {
                // do something
             } 
   }); 
   return view;
}
Raghunandan
źródło
To powoduje awarię fragmentu w moim projekcie, który używa API 21. Jakieś alternatywne poprawki / sugestie?
Darth Coder
@DarthCoder Nie sądzę. Musisz przedstawić kod. To może nie być związane z tym kodem zamieszczonym tutaj
Raghunandan
@Sanu nie ma nic wspólnego z Lollipop pre Lollipop. zadaj kolejne pytanie ze szczegółami ..
Raghunandan
Zdarzenie kliknięcia nigdy nie jest wyzwalane, gdy wykonuję powyższą odpowiedź.
Ted
4
Jestem trochę zaskoczony powyższymi komentarzami. Bez urazy, ale samo powiedzenie „to nie działa” bez zamieszczania jakichkolwiek szczegółów jest bardzo nieprofesjonalne… A jak dla mnie „to po prostu działa”.
zzheng,
15

Inną opcją może być zaimplementowanie we fragmencie View.OnClickListener i nadpisanie onClick (View v) w obrębie fragmentu. Jeśli chcesz, aby Twój fragment rozmawiał z działaniem, po prostu dodaj interfejs z wybraną metodą (metodami) i pozwól, aby aktywność zaimplementowała interfejs i zastąpiła jej metodę (metody).

public class FragName extends Fragment implements View.OnClickListener { 
    public FragmentCommunicator fComm;
    public ImageButton res1, res2;
    int c;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_test, container, false);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            fComm = (FragmentCommunicator) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement FragmentCommunicator");
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        res1 = (ImageButton) getActivity().findViewById(R.id.responseButton1);
        res1.setOnClickListener(this);
        res2 = (ImageButton) getActivity().findViewById(R.id.responseButton2);
        res2.setOnClickListener(this);
    }
    public void onClick(final View v) { //check for what button is pressed
            switch (v.getId()) {
                case R.id.responseButton1:
                  c *= fComm.fragmentContactActivity(2);
                  break;
                case R.id.responseButton2:
                  c *= fComm.fragmentContactActivity(4);
                  break;
                default:
                  c *= fComm.fragmentContactActivity(100);
                  break;
    }
    public interface FragmentCommunicator{
        public int fragmentContactActivity(int b);
    }



public class MainActivity extends FragmentActivity implements FragName.FragmentCommunicator{
    int a = 10;
    //variable a is update by fragment. ex. use to change textview or whatever else you'd like.

    public int fragmentContactActivity(int b) {
        //update info on activity here
        a += b;
        return a;
    }
}

http://developer.android.com/training/basics/firstapp/starting-activity.html http://developer.android.com/training/basics/fragments/communicating.html

cjayem13
źródło
3
Robię to cały czas, ale nadal uważam, że to brzydkie
Alexander Farber
Na początku też to robiłem, ale teraz stało się to prawie z drugiej ręki; plus faktycznie pomogło w bardziej złożonych aplikacjach, ale w przypadku czegoś podstawowego wydaje się przesadą.
cjayem13
@ cjayem13, czy możesz wyjaśnić, dlaczego jest to przesada?
eRaisedToX
9

To nie jest problem, to jest projekt Androida. Zobacz tutaj :

Każdy fragment należy zaprojektować jako modułowy i wielokrotnego użytku komponent aktywności. Oznacza to, że ponieważ każdy fragment definiuje swój własny układ i własne zachowanie z własnymi wywołaniami zwrotnymi cyklu życia, możesz uwzględnić jeden fragment w wielu działaniach, dlatego należy projektować do ponownego wykorzystania i unikać bezpośredniego manipulowania jednym fragmentem z innego fragmentu.

Możliwym obejściem byłoby zrobienie czegoś takiego w Twojej MainActivity:

Fragment someFragment;    

...onCreate etc instantiating your fragments

public void myClickMethod(View v){
    someFragment.myClickMethod(v);
}

a następnie w klasie Fragment:

public void myClickMethod(View v){
    switch(v.getid()){
       // Your code here
    }
 } 
CzarMatt
źródło
4

Inni już powiedzieli, że metody w onClick są wyszukiwane w działaniach, a nie we fragmentach. Niemniej jednak, jeśli naprawdę tego chcesz, jest to możliwe.

Zasadniczo każdy widok ma tag (prawdopodobnie null). Ustawiliśmy znacznik widoku głównego na fragment, który zawyżał ten widok. Wówczas można łatwo przeszukać widok rodziców i odzyskać fragment zawierający kliknięty przycisk. Teraz znajdujemy nazwę metody i używamy odbicia, aby wywołać tę samą metodę z pobranego fragmentu. Łatwy!

w klasie, która rozszerza Fragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_id, container, false);

    OnClickFragments.registerTagFragment(rootView, this); // <========== !!!!!

    return rootView;
}

public void onButtonSomething(View v) {
    Log.d("~~~","~~~ MyFragment.onButtonSomething");
    // whatever
}

wszystkie działania pochodzą z tego samego ButtonHandlingActivity:

public class PageListActivity extends ButtonHandlingActivity

ButtonHandlingActivity.java:

public class ButtonHandlingActivity extends Activity {

    public void onButtonSomething(View v) {
        OnClickFragments.invokeFragmentButtonHandlerNoExc(v);
//or, if you want to handle exceptions:
//      try {
//          OnClickFragments.invokeFragmentButtonHandler(v);
//      } catch ...
    }

}

Musi zdefiniować metody dla wszystkich programów obsługi XML onClick.

com / example / customandroid / OnClickFragments.java:

package com.example.customandroid;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import android.app.Fragment;
import android.view.View;

public abstract class OnClickFragments {
    public static class FragmentHolder {
        Fragment fragment;
        public FragmentHolder(Fragment fragment) {
            this.fragment = fragment;
        }
    }
    public static Fragment getTagFragment(View view) {
        for (View v = view; v != null; v = (v.getParent() instanceof View) ? (View)v.getParent() : null) {
            Object tag = v.getTag();
            if (tag != null && tag instanceof FragmentHolder) {
                return ((FragmentHolder)tag).fragment;
            }
        }
        return null;
    }
    public static String getCallingMethodName(int callsAbove) {
        Exception e = new Exception();
        e.fillInStackTrace();
        String methodName = e.getStackTrace()[callsAbove+1].getMethodName();
        return methodName;
    }
    public static void invokeFragmentButtonHandler(View v, int callsAbove) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
        String methodName = getCallingMethodName(callsAbove+1);
        Fragment f = OnClickFragments.getTagFragment(v);
        Method m = f.getClass().getMethod(methodName, new Class[] { View.class });
        m.invoke(f, v);
    }
    public static void invokeFragmentButtonHandler(View v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
        invokeFragmentButtonHandler(v,1);
    }
    public static void invokeFragmentButtonHandlerNoExc(View v) {
        try {
            invokeFragmentButtonHandler(v,1);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    public static void registerTagFragment(View rootView, Fragment fragment) {
        rootView.setTag(new FragmentHolder(fragment));
    }
}

A następną przygodą będzie zaciemnianie proguardów ...

PS

Oczywiście do Ciebie należy zaprojektowanie aplikacji tak, aby dane były obecne w modelu, a nie w działaniach lub fragmentach (które są kontrolerami z punktu widzenia MVC , Model-View-Controller ). Zobacz , co można zdefiniować za pomocą XML, plus zajęcia Widok niestandardowy (jeśli je zdefiniować, większość ludzi po prostu ponowne co już jest). Praktyczna zasada: jeśli niektóre dane z pewnością muszą przetrwać obrót ekranu, należą do Modelu .

18446744073709551615
źródło
3
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.writeqrcode_main, container, false);
    // Inflate the layout for this fragment

    txt_name = (TextView) view.findViewById(R.id.name);
    txt_usranme = (TextView) view.findViewById(R.id.surname);
    txt_number = (TextView) view.findViewById(R.id.number);
    txt_province = (TextView) view.findViewById(R.id.province);
    txt_write = (EditText) view.findViewById(R.id.editText_write);
    txt_show1 = (Button) view.findViewById(R.id.buttonShow1);

    txt_show1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e("Onclick","Onclick");

            txt_show1.setVisibility(View.INVISIBLE);
            txt_name.setVisibility(View.VISIBLE);
            txt_usranme.setVisibility(View.VISIBLE);
            txt_number.setVisibility(View.VISIBLE);
            txt_province.setVisibility(View.VISIBLE);
        }
    });
    return view;
}

W porządku !!!!

Pong Petrung
źródło
3
Czy mógłbyś wyjaśnić, jak to odpowiada na pytanie? Co masz na myśli mówiąc o swoim wyroku na końcu?
Teepeemm
3

Dla użytkowników Kotlin:

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?, 
    savedInstanceState: Bundle?) : View?
{
    // Inflate the layout for this fragment
    var myView = inflater.inflate(R.layout.fragment_home, container, false)
    var btn_test = myView.btn_test as Button
    btn_test.setOnClickListener {
        textView.text = "hunny home fragment"
    }

    return myView
}
Jayant Dhingra
źródło