Поиск по этому блогу

четверг, 16 ноября 2017 г.

Добавляем индикатор в Toolbar и в TabLayout

Иногда в приложения требуется добавить какой-то индикатор уведомляющий пользователя о новых нотификейшенах, пропущеных фильмах или объявлениях. Для этого часто приходится использовать библиотеки или какие-то костыли для отображения. В этой статье я раскажу о двух костылях которые мне пригождаются часто. первый это индикатор в тулбаре, и второй это количество «чего-то» в фрагменте, и нам нужно отображать в табе количество «этого».

Начну с простого. Отображение индикатора в тулбаре. Создаем проект, пустой, в котором у нас будет только активность и леяут к ней. Так же нам нужно будет создать еще дополнительно вьюху в которой у нас будет кастомный вид этой иконки, и по надобности мы будем ее применять.


Индикатор на иконке в Toolbar


Для начала нам нужно создать саму менюшку. В ней у нас будет две иконки. Они будут вот так выглядеть.

  

menu_main.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/groups"
        android:title="My Groups"
        android:icon="@mipmap/icon_invited_users"
        app:showAsAction="always" />

    <item
        android:id="@+id/panics"
        android:title="Panics"
        android:icon="@mipmap/icon_panic_counts"
        app:showAsAction="always" />

</menu>

А еще нам нужно создать точку которая будет поверх иконки.

notification_round_badge.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/colorAccent" />
    <corners
        android:bottomLeftRadius="2dp"
        android:bottomRightRadius="2dp"
        android:topLeftRadius="2dp"
        android:topRightRadius="2dp" />
</shape>

А еще нам понадобится иконка с точкой, для кастомизации вида нашей иконки.

view_menu_icon_badge.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/counterPanel"
    android:layout_width="32dp"
    android:layout_height="32dp"
    android:background="@mipmap/icon_invited_users">

    <LinearLayout
        android:id="@+id/counterValuePanel"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="bottom|left"
        android:gravity="bottom">

        <ImageView
            android:id="@+id/counterBackground"
            android:layout_width="7dp"
            android:layout_height="7dp"
            android:background="@drawable/notification_round_badge" />

    </LinearLayout>
</FrameLayout>

Ну, а теперь нам осталось еще это все собрать в кучу, активити. 

MainToolbarActivity.java
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;

public class MainToolbarActivity extends AppCompatActivity {

    private boolean isYouWantToShowBadge = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_toolbar_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_main, menu);

        if(isYouWantToShowBadge)
            menu.findItem(R.id.groups).setIcon(buildCounterDrawable(R.mipmap.icon_invited_users));
        menu.findItem(R.id.panics).setIcon(buildCounterDrawable(R.mipmap.icon_panic_counts));

        return true;
    }

    private Drawable buildCounterDrawable(int backgroundImageId) {
        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.view_menu_icon_badge, null);
        view.setBackgroundResource(backgroundImageId);

        view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());

        view.setDrawingCacheEnabled(true);
        view.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
        view.setDrawingCacheEnabled(false);

        return new BitmapDrawable(getResources(), bitmap);
    }
}

И вот тут мы в onCreateOptionsMenu() указываем что будем использовать как менюшку, дальше я для примера сделал как сделать иконку без пина, и с пином. Делаю проверку и отображаю иконки. Для установки пина мы используем метод buildCounterDrawable() который берет наш кастомный файл с разметкой, и указываем ему какую иконку использовать и что на нее накладывать точку и превращаем в битмап для отображения. 

В итоге у вас получится вот такая штука, если вы брали код который я предоставил.


Индикатор на иконке в TabLayout


Для того что бы добавить нам цифры в таб, нам нужно взять таб и наложить на него кастомную вьюху. А в кастомном табе мы уже будем менять иконки, текст и т.д.

Для начала нам нужно создать все нужные вьюхи, а это кастомный вид таба, и заливка баджа. Начнем мы с заливки.

badge_background.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item xmlns:android="http://schemas.android.com/apk/res/android">
        <shape android:shape="oval">
            <solid android:color="@color/colorAccent" />
        </shape>
    </item>
</layer-list>

Просто делаем круглую заливку что бы подложить под текст в кастомном табе. Дальше добавляем эту заливку в кастомный таб.

view_tabs_with_badge.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:gravity="center_vertical|center_horizontal"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textAllCaps="true"
        android:textColor="@android:color/black"
        android:textSize="14sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/count"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_marginLeft="10dp"
        android:background="@drawable/badge_background"
        android:gravity="center"
        android:text="1"
        android:textColor="@color/colorPrimary"
        android:textSize="14sp" />

</LinearLayout>

И вот таким образом мы подготовили все для создания таба. Дальше нам нужно добавить в активити TabLayout и ViewPager. 

activity_tabs_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:elevation="1dp"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:tabIndicatorColor="@color/colorAccent"
        app:tabSelectedTextColor="@android:color/black"
        app:tabTextColor="@android:color/black" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

Настроили красоту, что бы там цвет нам нужный был, что бы оно было нужного размера и цвета… дальше нам нужно создать адаптер для пейджинга ну и потом все это подключить в активити.

PagerAdapter.java
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

import java.util.ArrayList;
import java.util.List;

public class PagerAdapter extends FragmentPagerAdapter {

    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    public PagerAdapter(FragmentManager manager) {
        super(manager);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

    public void addFragment(Fragment fragment, String title) {
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mFragmentTitleList.get(position);
    }

    @Override
    public int getItemPosition(Object object){
        return super.getItemPosition(object);

    }
}

В общем, создаем дав списка, первый хранит список с фрагментами которые будем отображать, второй хранит тайтлы с названиями табов. В зависимости от позиции будем отображать нужный таб. Дальше создадим три фрагмента которые будем отображать в пейджере.

Fragment1.java, Fragment2.java, Fragment3.java
public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_tab, container, false);
        return rootView;
    }
}

Создал число для вида, в них ничего не будет, просто пустые фрагменты. Совершенно одинаковые, меняется только название фрагмента. Ну и дальше объеденяем все в акивити. 

MainTabsActivity.java
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

import com.project.dajver.tabsbadgeexample.adapter.PagerAdapter;
import com.project.dajver.tabsbadgeexample.section.Fragment1;
import com.project.dajver.tabsbadgeexample.section.Fragment2;
import com.project.dajver.tabsbadgeexample.section.Fragment3;

import java.util.ArrayList;

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainTabsActivity extends AppCompatActivity {

    private ArrayList<String> tabTitle = new ArrayList<>();
    private int[] unreadCount = {0, 0, 0};

    @BindView(R.id.tab_layout)
    TabLayout tabLayout;
    @BindView(R.id.pager)
    ViewPager viewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tabs_main);
        ButterKnife.bind(this);

        tabTitle.add(getString(R.string.title_my_friends_screen));
        tabTitle.add(getString(R.string.title_my_groups_screen));
        tabTitle.add(getString(R.string.title_my_invites_screen));

        unreadCount[1] = 23;
        unreadCount[2] = 3;
        viewPager.setOffscreenPageLimit(3);
        setupViewPager(viewPager);
        tabLayout.setupWithViewPager(viewPager);
        setupTabIcons();
    }

    private void setupViewPager(ViewPager viewPager) {
        PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());
        Fragment1 friendsTabFragment = new Fragment1();
        Fragment2 myGroupsTabFragment = new Fragment2();
        Fragment3 invitesTabFragment = new Fragment3();

        adapter.addFragment(friendsTabFragment, getString(R.string.title_my_friends_screen));
        adapter.addFragment(myGroupsTabFragment, getString(R.string.title_my_groups_screen));
        adapter.addFragment(invitesTabFragment, getString(R.string.title_my_invites_screen));
        viewPager.setAdapter(adapter);
    }

    private void setupTabIcons() {
        for(int i = 0; i < tabTitle.size(); i++) {
            View view = getLayoutInflater().inflate(R.layout.view_tabs_with_badge,null);
            TextView textView = view.findViewById(R.id.title);
            TextView countView = view.findViewById(R.id.count);
            textView.setText(tabTitle.get(i));
            if(unreadCount[i] > 0) {
                countView.setVisibility(View.VISIBLE);
                countView.setText(String.valueOf(unreadCount[i]));
            } else
                countView.setVisibility(View.GONE);
            tabLayout.getTabAt(i).setCustomView(view);
        }
    }
}

И так, в onCreate() мы добавляем тайтлы табов, у нас их будет три, группы, друзья и приглашения. дальше задаем что в первом табе у нас не будет никаких баджей, а на остальных двух будут. 

В setupViewPager() мы создаем фрагменты которые будут привязаны к табам и отправляем это все в адаптер. С помощью setupTabIcons() мы создаем наши табы с иконками и в зависимости от того надо нам оно отображать бадж или нет мы делаем его видимым или невидимым и в итоге добавялем во вьюху.

И в итоге должно получится что-то на подобии штуки как ниже

Если у вас все получилось — поздравляю, вы тру кодер который смог!

Исходники разделены на два модуля, tabs и toolbar. Думаю разобраться будет не сложно что-где :)

Исходники:
GitHub