¿Cómo agrego un Fragmento a una Actividad con una vista de contenido creada mediante programación?

240

Quiero agregar un Fragmento a una Actividad que implemente su diseño mediante programación. Revisé la documentación de Fragment pero no hay muchos ejemplos que describan lo que necesito. Aquí está el tipo de código que intenté escribir:

public class DebugExampleTwo extends Activity {

    private ExampleTwoFragment mFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FrameLayout frame = new FrameLayout(this);
        if (savedInstanceState == null) {
            mFragment = new ExampleTwoFragment();
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(frame.getId(), mFragment).commit();
        }

        setContentView(frame);
    }
}

...

public class ExampleTwoFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, 
                             ViewGroup container, 
                             Bundle savedInstanceState) {
        Button button = new Button(getActivity());
        button.setText("Hello There");
        return button;
    }
}

Este código se compila pero se bloquea al inicio, probablemente porque mi FragmentTransaction.add()es incorrecto. ¿Cuál es la forma correcta de hacer esto?

Tony Wong
fuente

Respuestas:

198

Resulta que hay más de un problema con ese código. Un fragmento no se puede declarar de esa manera, dentro del mismo archivo java que la actividad, pero no como una clase interna pública. El marco espera que el constructor del fragmento (sin parámetros) sea público y visible. Mover el fragmento a la Actividad como una clase interna, o crear un nuevo archivo java para el fragmento corrige eso.

El segundo problema es que cuando agrega un fragmento de esta manera, debe pasar una referencia a la vista que contiene el fragmento, y esa vista debe tener una identificación personalizada. El uso de la identificación predeterminada bloqueará la aplicación. Aquí está el código actualizado:

public class DebugExampleTwo extends Activity {

    private static final int CONTENT_VIEW_ID = 10101010;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FrameLayout frame = new FrameLayout(this);
        frame.setId(CONTENT_VIEW_ID);
        setContentView(frame, new LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

        if (savedInstanceState == null) {
            Fragment newFragment = new DebugExampleTwoFragment();
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(CONTENT_VIEW_ID, newFragment).commit();
        }
    }

    public static class DebugExampleTwoFragment extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            EditText v = new EditText(getActivity());
            v.setText("Hello Fragment!");
            return v;
        }
    }
}
Tony Wong
fuente
115
Si solo desea usar el fragmento como la vista de contenido de nivel superior de la actividad, puede usarlo ft.add(android.R.id.content, newFragment). Solo es necesario crear un diseño personalizado y configurar su id si el contenedor del fragmento no es la vista de contenido de la actividad.
Tony Wong
25
En lugar de codificar la identificación, puede definirla en XML y hacer referencia a ella como normal (R.id.myid).
Jason Hanley
1
No sé cómo hacerlo, pero recuerda que una identificación solo tiene que ser única en el ámbito en el que necesitas usarla.
Jason Hanley
2
el id solo necesita ser único en su nivel dentro de la jerarquía actual del diseño que lo contiene. Digamos que está envuelto en un diseño lineal, solo necesita ser único entre las otras vistas dentro de ese diseño lineal.
Shaun
1
Puede crear una ID dinámicamente usando setId (View.NO_ID) y luego getId () para ver qué era.
enl8enmentnow
71

Esto es lo que se me ocurrió después de leer el comentario de Tony Wong :

public class DebugExampleTwo extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addFragment(android.R.id.content,
                    new DebugExampleTwoFragment(),
                    DebugExampleTwoFragment.FRAGMENT_TAG);
    }

}

...

public abstract class BaseActivity extends Activity {

    protected void addFragment(@IdRes int containerViewId,
                               @NonNull Fragment fragment,
                               @NonNull String fragmentTag) {
        getSupportFragmentManager()
                .beginTransaction()
                .add(containerViewId, fragment, fragmentTag)
                .disallowAddToBackStack()
                .commit();
    }

    protected void replaceFragment(@IdRes int containerViewId,
                                   @NonNull Fragment fragment,
                                   @NonNull String fragmentTag,
                                   @Nullable String backStackStateName) {
        getSupportFragmentManager()
                .beginTransaction()
                .replace(containerViewId, fragment, fragmentTag)
                .addToBackStack(backStackStateName)
                .commit();
    }

}

...

public class DebugExampleTwoFragment extends Fragment {

    public static final String FRAGMENT_TAG = 
        BuildConfig.APPLICATION_ID + ".DEBUG_EXAMPLE_TWO_FRAGMENT_TAG";

    // ...

}

Kotlin

Si está utilizando Kotlin, asegúrese de echar un vistazo a lo que proporcionan las extensiones de Kotlin de Google o simplemente escriba el suyo.

JJD
fuente
¡No lo hagas! Verifique if (savedInstanceState == null)antes de la creación de fragmentos, o después de rotar una pantalla tendrá dos fragmentos o reordenamiento de fragmentos. ¡No use el addmétodo en absoluto! Sólo replace. O tendrás un comportamiento extraño.
CoolMind
¿Dónde obtiene el valor de "backStackStateName"? (Al usar la función de reemplazo)
vikzilla
@vikzilla Puedes encontrar respuestas bastante buenas aquí y en los documentos . En resumen: la backStackStateNamecadena es algo que usted define.
JJD
34
    public class Example1 extends FragmentActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
          DemoFragment fragmentDemo = (DemoFragment) 
          getSupportFragmentManager().findFragmentById(R.id.frame_container);
          //above part is to determine which fragment is in your frame_container
          setFragment(fragmentDemo);
                       (OR)
          setFragment(new TestFragment1());
        }

        // This could be moved into an abstract BaseActivity 
        // class for being re-used by several instances
        protected void setFragment(Fragment fragment) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = 
                fragmentManager.beginTransaction();
            fragmentTransaction.replace(android.R.id.content, fragment);
            fragmentTransaction.commit();
        }
    }

Para agregar un fragmento a una actividad o actividad de fragmento, se requiere un contenedor. Ese contenedor debe ser un " Framelayout", que puede incluirse en xml o de lo contrario puede usar el contenedor predeterminado para ese " android.R.id.content" para eliminar o reemplazar un fragmento en Activity.

main.xml

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 <!-- Framelayout to display Fragments -->
   <FrameLayout
        android:id="@+id/frame_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/imagenext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="16dp"
        android:src="@drawable/next" />
</RelativeLayout>
anand krish
fuente
29

Después de leer todas las respuestas, se me ocurrió una manera elegante:

public class MyActivity extends ActionBarActivity {

 Fragment fragment ;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    FragmentManager fm = getSupportFragmentManager();
    fragment = fm.findFragmentByTag("myFragmentTag");
    if (fragment == null) {
        FragmentTransaction ft = fm.beginTransaction();
        fragment =new MyFragment();
        ft.add(android.R.id.content,fragment,"myFragmentTag");
        ft.commit();
    }

}

básicamente, no necesita agregar un marco de diseño como contenedor de su fragmento, sino que puede agregar directamente el fragmento al contenedor de vista de raíz de Android

IMPORTANTE: no utilice reemplazar fragmento como la mayoría del enfoque que se muestra aquí, a menos que no le importe perder el estado de instancia de variable de fragmento durante el proceso de recreación .

Xenione
fuente
gracias por la respuesta, esto agrega la pestaña de fragmentos a toda la pantalla? pero, ¿cómo se agrega al diseño de un marco o al buscapersonas?
flankechen
6
public abstract class SingleFragmentActivity extends Activity {

    public static final String FRAGMENT_TAG = "single";
    private Fragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            fragment = onCreateFragment();
           getFragmentManager().beginTransaction()
                   .add(android.R.id.content, fragment, FRAGMENT_TAG)
                   .commit();
       } else {
           fragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
       }
   }

   public abstract Fragment onCreateFragment();

   public Fragment getFragment() {
       return fragment;
   }

}

utilizar

public class ViewCatalogItemActivity extends SingleFragmentActivity {
    @Override
    public Fragment onCreateFragment() {
        return new FragmentWorkShops();
    }

}
usuario2212515
fuente
6

Para API nivel 17 o superior, View.generateViewId()resolverá este problema. El método de utilidad proporciona una identificación única que no se usa en el tiempo de compilación.

Sfseyhan
fuente
3
¡Bienvenido a Stack Overflow! Si bien esto puede responder teóricamente la pregunta, sería preferible incluir aquí las partes esenciales de la respuesta y proporcionar el enlace para referencia.
SuperBiasedMan
3

Para adjuntar fragmentos a una actividad mediante programación en Kotlin, puede consultar el siguiente código:

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

            // create fragment instance
            val fragment : FragmentName = FragmentName.newInstance()

            // for passing data to fragment
            val bundle = Bundle()
            bundle.putString("data_to_be_passed", DATA)
            fragment.arguments = bundle

            // check is important to prevent activity from attaching the fragment if already its attached
            if (savedInstanceState == null) {
                supportFragmentManager
                    .beginTransaction()
                    .add(R.id.fragment_container, fragment, "fragment_name")
                    .commit()
            }
        }

    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".ui.MainActivity">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

FragmentName.kt

class FragmentName : Fragment() {

    companion object {
        fun newInstance() = FragmentName()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        // receiving the data passed from activity here
        val data = arguments!!.getString("data_to_be_passed")
        return view
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
    }

}

Si está familiarizado con las Extensiones en Kotlin, puede incluso mejorar este código siguiendo este artículo.

bhavya_karia
fuente