#java #android #android-fragments #fragment
#java #Android #android-фрагменты #фрагмент
Вопрос:
Я пытаюсь создать простое приложение со списком продуктов и подробной информацией о продукте. Что касается моих первоначальных попыток, я создал приложение, используя BottomNavigationView, где внизу есть маленькие значки, и вы можете переключать фрагменты, нажимая на один из них. В настоящее время у меня есть 4 значка на нижней панели навигации, и вот 4 значка:
- Продукты (ProductsFragment)
- Избранное (FavoritesFragment)
- Профиль (ProfileFragment)
- Корзина (CartFragment)
Эти 4 фрагмента переключаются в моем HomeActivity, вот как я их переключаю:
HomeActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
bottomNavbar = findViewById(R.id.nav_view);
bottomNavbar.setOnNavigationItemSelectedListener(navListener);
homeContainer = findViewById(R.id.homeContainer);
createSnackbar(homeContainer);
viewModel = new HomeViewModel();
viewModel.getCartCounterLiveData().observe(this, CartCounterObserver);
createBadges(bottomNavbar);
}
private BottomNavigationView.OnNavigationItemSelectedListener navListener = new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
BaseRTFFragment selectedFragment = null;
String stackName;
switch(item.getItemId()) {
case R.id.nav_home:
selectedFragment = new ProductsFragment();
break;
case R.id.nav_favorites:
selectedFragment = new FavoritesFragment();
break;
case R.id.nav_profile:
selectedFragment = new ProfileFragment();
break;
case R.id.nav_cart:
selectedFragment = new CartFragment();
break;
}
navigateToFragment(selectedFragment);
return true;
}
};
private void navigateToFragment(BaseRTFFragment selectedFragment) {
FragmentTransaction fTrans = getSupportFragmentManager().beginTransaction();
// fTrans.addToBackStack(selectedFragment.getFragmentName());
fTrans.replace(R.id.nav_host_fragment, selectedFragment).commit();
}
Так что это всего лишь простой вызов replace() . На данный момент он работает нормально, фрагменты переключаются без черного экрана между ними
Кстати, во фрагменте PRODUCTS есть текст поиска, кнопки фильтра категорий и разбитое на страницы RecyclerView для отображения списка продуктов. Смотрите код ниже:
ProductsFragment.java
public class ProductsFragment extends BaseRTFFragment<FragmentProductsBinding>
implements Observer<PagedList<Product>>, ProductsAdapter.OnProductClickListener, CategoryAdapter.OnCategoryClickListener {
ProductsViewModel viewModel;
ProductsAdapter productsAdapter;
int selectedCategory;
String currentSearchText;
ProductsEventsListener listener;
private CategoryAdapter categoryAdapter;
private String fragmentName = "Products";
@Override
protected int getFragmentLayout() {
return R.layout.fragment_products;
}
@Override
public String getFragmentName() { return fragmentName; }
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel = new ProductsViewModel();
initProductsAdapter();
initCategoryAdapter();
initializeSearch();
loadProducts();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
listener = (ProductsEventsListener) context;
} catch (ClassCastException castException) {
throw new Error("The activity does not implement the listener");
}
}
private void initProductsAdapter() {
productsAdapter = new ProductsAdapter(this);
dataBinding.productsRecyclerView.setAdapter(productsAdapter);
}
private void initCategoryAdapter() {
Query cQuery = initCategoryQuery();
cQuery.get().addOnCompleteListener(categoryListener);
}
private OnCompleteListener<QuerySnapshot> categoryListener = new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if(task.isSuccessful()) {
List<Category> categories = task.getResult().toObjects(Category.class);
categoryAdapter = getCategoryAdapter(categories);
dataBinding.categoryRecyclerView.setAdapter(categoryAdapter);
}
}
};
private CategoryAdapter getCategoryAdapter(List<Category> categories) {
return new CategoryAdapter(categories, this);
}
private Query initCategoryQuery() {
FirebaseFirestore mFirestore = FirebaseFirestore.getInstance();
return mFirestore.collection("categories")
.orderBy("name", Query.Direction.ASCENDING);
}
private void loadProducts() {
viewModel.pagedListLiveData.observe(getViewLifecycleOwner(), this);
}
private void reloadProducts() {
viewModel.replaceSubscription(this, null, 0);
loadProducts();
}
private void loadSearchedProducts(String searchText, int category) {
viewModel.replaceSubscription(this, searchText, category);
loadProducts();
}
@Override
public void onChanged(PagedList<Product> products) {
productsAdapter.submitList(products);
hideProgressBar();
// toggleEmptyLayout(products.size());
}
private void toggleEmptyLayout(int count) {
if(count > 0) {
dataBinding.productsRecyclerView.setVisibility(View.VISIBLE);
dataBinding.emptyResultLayout.setVisibility(View.GONE);
} else {
dataBinding.productsRecyclerView.setVisibility(View.GONE);
dataBinding.emptyResultLayout.setVisibility(View.VISIBLE);
}
}
private void hideProgressBar() {
dataBinding.progressBar.setVisibility(View.GONE);
}
public int getSelectedCategory() {
return selectedCategory;
}
@Override
public void onProductClick(Product product) {
displayProductName(product.getName() " has been clicked!");
listener.OnProductClick(product);
}
@Override
public void onAddProductToCart(Product product) {
// displayProductName(product.getName() " has been added to cart");
listener.OnAddProductToCart(product);
}
@Override
public void onFavoriteProduct(Product product) {
listener.OnFavoriteProduct(product);
}
private void displayProductName(String name) {
Toast.makeText(getActivity(), name, Toast.LENGTH_SHORT).show();
}
private void initializeSearch() {
dataBinding.searchInput.addTextChangedListener(searchTextWatcher);
}
private TextWatcher searchTextWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable searchText) {
String sText = searchText.toString().toLowerCase();;
if (sText != currentSearchText) {
currentSearchText = sText;
loadSearchedProducts(currentSearchText, selectedCategory);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
};
@Override
public void onCategoryClick(int selectedCategoryId) {
selectedCategory = selectedCategoryId;
loadSearchedProducts(currentSearchText, selectedCategory);
}
}
All of my Fragments inherits a custom base fragment which just inherits from the android Fragment, see code below:
BaseRTFFragment.java
public abstract class BaseRTFFragment<T extends ViewDataBinding> extends Fragment {
protected T dataBinding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
dataBinding = DataBindingUtil.inflate(inflater, getFragmentLayout(), null, false);
return dataBinding.getRoot();
}
protected abstract int getFragmentLayout();
public abstract String getFragmentName();
@Override
public void onDestroyView() {
super.onDestroyView();
dataBinding = null;
}
}
So now everything is fine, its now time to add the product detail page.
For this one, I’ve created a new ProductFragment (take note no ‘s’ in the name :))
ProductFragment.java
public class ProductFragment extends BaseRTFFragment<FragmentProductBinding> implements Observer<Product> {
ProductViewModel viewModel;
ProductEventsListener listener;
private String fragmentName = "Product";
@Override
protected int getFragmentLayout() {
return R.layout.fragment_product;
}
@Override
public String getFragmentName() { return fragmentName; }
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
int productId = getArguments().getInt("productId");
viewModel = new ProductViewModel(productId);
viewModel.getProduct(productId).observe(getViewLifecycleOwner(), this);
return super.onCreateView(inflater, container, bundle);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
listener = (ProductEventsListener) context;
} catch (ClassCastException castException) {
throw new Error("The activity does not implement the listener");
}
}
@Override
public void onChanged(Product product) {
dataBinding.productTitle.setText(product.getName());
}
}
For the layout for this fragment, see the code below:
fragment_product.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@ id/productFragmentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@ id/productAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@ id/productCollapseToolbar"
android:layout_width="match_parent"
android:layout_height="256dp"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:titleEnabled="false">
<ImageView
android:id="@ id/htab_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/ic_default_product"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"/>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.3"
android:background="@android:color/black"
android:fitsSystemWindows="true"/>
<ImageButton
android:id="@ id/backBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:tint="@color/colorSecondary"
android:padding="@dimen/btn_padding"
android:layout_margin="@dimen/btn_padding"
android:onClick="OnNavigateBack"
android:src="@drawable/ic_previous"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/default_page_padding"
android:orientation="vertical">
<TextView
android:id="@ id/productTitle"
style="@style/ProductTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/btn_padding"
android:text="Growers Mix"/>
<TextView
android:id="@ id/productDescription"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Test"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Обратите внимание на ImageButton, я объявил метод OnNavigateBack в HomeActivity.java потому что, объявляя об этом на ProductFragment.java вызывает некоторые ошибки, говорящие о том, что он не может найти подобный метод, и поэтому я объявляю его в HomeActivity. Вот как это выглядит:
HomeActivity.java:OnNavigateBack
@Override
public void OnNavigateBack() {
navigateToFragment(new ProductsFragment());
}
private void navigateToFragment(BaseRTFFragment selectedFragment) {
FragmentTransaction fTrans = getSupportFragmentManager().beginTransaction();
// fTrans.addToBackStack(selectedFragment.getFragmentName());
fTrans.replace(R.id.nav_host_fragment, selectedFragment).commit();
}
@Override
public void OnProductClick(Product product) {
// displayProductName(product.getName() " should be viewed in a separate fragment");
Bundle bundle = new Bundle();
bundle.putInt("productId", product.getId());
ProductFragment productFragment = new ProductFragment();
productFragment.setArguments(bundle);
navigateToFragment(productFragment);
}
Это просто вызов метода navigateToFragment() из предыдущего. Но на этот раз каждый раз, когда я нажимаю кнопку «Назад» из ProductFragment, он отображает черный экран не менее 1 секунды и загружает список продуктов. Я не уверен, что кажется неправильным, потому что я просто загружаю фрагмент так же, как и другие. Я что-то пропустил? Или подход, который я сделал, был неправильным? Что мне делать? Пожалуйста, помогите. Заранее спасибо.
Ответ №1:
Найдено решение по этому вопросу, похоже, привязка не работает должным образом с использованием свойства android: onClick в макете фрагмента. Поэтому я возвращаюсь к старому способу привязки событий щелчка, связывая его, устанавливая OnClickListener на кнопке «Назад»:
СДЕЛАТЬ:
ProductFragment.java
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
bindEvents();
}
private void bindEvents() {
dataBinding.backBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onNavigateBack();
}
});
}
Я НЕ ДЕЛАЮ С ЭТОГО МОМЕНТА НИКОГДА:
fragment_product.xml
...
<ImageButton
....
android:id="@ id/backBtn"
android:onClick="onProductClick"
... />
...