Hi dev, kesempatan kali ini saya ingin membahas tentang implementasi parallax untuk header tabview. Menurut kalian apa itu parallax? menurut saya, parallax adalah seni transisi antara dua halaman berbeda menggunakan animasi.

Sebetulnya, teknik parallax ini telah lama populer di platform web, namun baru-baru ini banyak para developer android terinspirasi dengan hal itu dan ingin menerapkannya di aplikasi mobile mereka, maka tak ada salahnya jika saya berbagi solusi alternatif tanpa menggunakan library pihak ketiga.

Anda akan segera mengetahui bahwa semua kebutuhan ini adalah TabLayout
. Tapi tunggu, pegang kudamu! Saya baru saja memasukkan TabLayout
dan inilah yang malah terjadi.

Berbeda dengan TabView Material Design pada umumnya, kalau di sini Tab-nya transparan. Jadi jelas, solusi plug and play tidak berfungsi pada kasus ini.
Sepertinya diperlukan beberapa penyesuaian. Jadi sebelum saya langsung menempelkan seluruh kode, mari kita lihat kerangka tata letaknya.
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout>
<android.support.design.widget.CollapsingToolbarLayout>
<ImageView />
<android.support.v7.widget.Toolbar />
<android.support.design.widget.TabLayout />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
CollapsingToolbarLayout seperti FrameLayout
. Jadi perhatikan urutan elemen di dalamnya. Elemen apa pun yang Anda tempatkan terakhir, adalah yang muncul di atas. Penempatan posisi ini penting agar parallax scrolling berfungsi.
Selain itu, ImageView-lah yang memungkinkan pengguliran paralaks. Kita bisa memberi tahu ImageView
untuk menggulir paralaks dengan satu atribut. Lebih lanjut akan kita bahas nanti.
Jadi sekarang setelah Anda memiliki gagasan tentang struktur tata letak, lihat kode tata letak XML ini.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/htab_maincontent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/htab_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/htab_collapse_toolbar"
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/header"
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"/>
<android.support.v7.widget.Toolbar
android:id="@+id/htab_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="top"
android:layout_marginBottom="48dp"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<android.support.design.widget.TabLayout
android:id="@+id/htab_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:tabIndicatorColor="@android:color/white"
app:tabSelectedTextColor="@android:color/white"
app:tabTextColor="@color/white_70"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/htab_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
Saya tahu kodenya sangat panjang. Tapi butuh usaha dua kali untuk mencoba efek paralaks menggunakan Java. Jadi, jika Anda sudah pulih dari keterkejutan, mari kita lihat apa yang terjadi pada kode di atas.
Penjelasan
Beberapa atribut inilah yang membuat parallax scroll berfungsi. Mari kita bahas satu per satu.
- 256dp adalah angka yang bagus untuk sebuah collapsible header view.
- Ini bukan Ruang Fleksibel dengan pola Gambar. Kami tidak ingin judul Toolbar diciutkan. Kami ingin itu diperbaiki di atas. Jadi kami menonaktifkan judul
collapsible
.
- Flag yang memicu efek paralaks saat menggulir halaman.
- Scrim yang membuat teks Tab lebih mudah dibaca dengan latar belakang tajuk yang padat.
- Ingatlah bahwa
CollapsingToolbarLayout
adalah perpanjangan dari FrameLayout
. Atribut ini memastikan Toolbar
tetap di atas.
- TabLayout secara default memiliki ketinggian 48dp. Memberi tahu Toolbar kami untuk memiliki margin bawah yang sama, menghindari masalah tumpang tindih yang kami lihat di atas.
- Memastikan
Toolbar
secara konsisten disematkan ke atas. Jika tidak, saat Anda mulai menggulir, Toolbar
akan menggulir ke luar layar.
- Memastikan
TabLayout
menempel di bagian bawah header.
Jika kailan masih pusing dengan poin-poin penjelasan di atas, semoga visualisasi di bawah ini bisa sedikit membantu memberikan pencerahan.

Setup ViewPager
Jika Anda lupa, Tab memerlukan ViewPager
untuk digunakan, jadi mari kita tangani itu di Activity.java.
Mulailah dengan membuat dummy Fragment
untuk menampilkan beberapa konten, Fragment
sebagai permulaan tampilkan saja RecyclerView
.
Selain itu, warna latar belakang Fragment
akan berubah bergantung pada posisinya di ViewPager
.
public static class DummyFragment extends Fragment {
int color;
public DummyFragment() {
}
@SuppressLint("ValidFragment")
public DummyFragment(int color) {
this.color = color;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dummy_fragment, container, false);
final FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.dummyfrag_bg);
frameLayout.setBackgroundColor(color);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.dummyfrag_scrollableview);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity().getBaseContext());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setHasFixedSize(true);
DessertAdapter adapter = new DessertAdapter(getContext());
recyclerView.setAdapter(adapter);
return view;
}
}
Perhatikan bahwa DummyFragment
hanyalah sebuah placeholder yang saya gunakan dengan warna latar belakang berbeda untuk setiap Tab saya. DessertAdapter
adalah RecyclerView.Adapter
yang saya tulis untuk menampilkan konten yang dapat digulir yang dapat mendemonstrasikan efek paralaks.
Di sisi lain, Anda harus menentukan differentFragment
yang sesuai, untuk setiap Tab Anda.
ViewPager Adapter
Adaptor minimalis untuk ViewPager
kami (menampung 3 Fragmen).
private static class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mmFragmentTitleList FragmentList = new ArrayList<>();
private final List<String> = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFrag(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Menghubungkan ViewPager dengan TabLayout
Terakhir kita akan melampirkan ViewPager
ke TabLayout
di metode onCreate()
. Kami menggunakan OnTabSelectedListener
untuk menangani pengalihan Fragmen saat diklik.
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
switch (tab.getPosition()) {
case 0:
// TODO
break;
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) { }
@Override
public void onTabReselected(TabLayout.Tab tab) {}
});
Jangan lupa untuk mengisi Adaptor ViewPager
dengan Fragment
Anda. Saya menambahkan 3 Fragment ke Adaptor, masing-masing punya warna background tersendiri.
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(
getSupportFragmentManager());
adapter.addFrag(new DummyFragment(
ContextCompat.getColor(this, R.color.cyan_50)), "Cyan");
adapter.addFrag(new DummyFragment(
ContextCompat.getColor(this, R.color.amber_50)), "Amber");
adapter.addFrag(new DummyFragment(
ContextCompat.getColor(this, R.color.purple_50)), "Purple");
viewPager.setAdapter(adapter);
}
Metode setupViewPager()
hanya menginisialisasi Fragment
dan menambahkannya ke ViewPager
.
Warna Tab Dinamis dengan Palette API
Warna yang Anda lihat diambil oleh Toolbar
+ TabLayout
, diambil dari gambar header. Atas kebaikan Palette API. Ini dinamis dan warnanya bervariasi tergantung pada gambar.
Dengan menggunakan Palette API, kami dipermudah untuk menyetel warna Toolbar
, TabLayout
, dan StatusBar
.
try {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.header);
Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
@SuppressWarnings("ResourceType")
@Override
public void onGenerated(Palette palette) {
int vibrantColor = palette.getVibrantColor(R.color.primary_500);
int vibrantDarkColor = palette.getDarkVibrantColor(R.color.primary_700);
collapsingToolbarLayout.setContentScrimColor(vibrantColor);
collapsingToolbarLayout.setStatusBarScrimColor(vibrantDarkColor);
}
});
} catch (Exception e) {
// if Bitmap fetch fails, fallback to primary colors
Log.e(TAG, "onCreate: failed to create bitmap from background", e.fillInStackTrace());
collapsingToolbarLayout.setContentScrimColor(
ContextCompat.getColor(this, R.color.primary_500)
);
collapsingToolbarLayout.setStatusBarScrimColor(
ContextCompat.getColor(this, R.color.primary_700)
);
}
Perhatikan bahwa memanggil Palette API sebenarnya dilakukan di blok try-catch
. Ini adalah salah satu praktik terbaik mengatasi kegagalan (error handling). Di sini, Palette API bisa gagal jika kami tidak bisa mendapatkan Bitmap. Sebagai gantinya, kami menggunakan warna utama aplikasi kami.
Final Output
Dengan semua yang ada, jalankan aplikasi Anda dengan cara menggulirnya dan lihatlah betapa menawannya efek parallax header itu.
