-
-
Save eneim/55df01e092520a6d33a4 to your computer and use it in GitHub Desktop.
| package im.ene.lab.android.widgets; | |
| import android.content.Context; | |
| import android.graphics.PorterDuff; | |
| import android.graphics.drawable.Drawable; | |
| import android.os.Build; | |
| import android.support.annotation.NonNull; | |
| import android.support.annotation.Nullable; | |
| import android.support.design.widget.TabLayout; | |
| import android.util.AttributeSet; | |
| import android.util.SparseArray; | |
| import android.view.LayoutInflater; | |
| import android.view.View; | |
| import android.widget.ImageView; | |
| import android.widget.TextView; | |
| import im.ene.lab.android.R; | |
| /** | |
| * Created by eneim on 9/2/15. | |
| * <p/> | |
| * A custom TabLayout with Builder support for customizing Tabs. | |
| * <p/> | |
| * Since every Tab must be attached to a parent TabLayout, it's reasonable to have an inner | |
| * Builder for every Tab in TabLayout, but not a global TabLayout#Builder. Builder is not strictly | |
| * follow Builder design pattern. | |
| */ | |
| public class BadgeTabLayout extends TabLayout { | |
| private final SparseArray<Builder> mTabBuilders = new SparseArray<>(); | |
| public BadgeTabLayout(Context context) { | |
| super(context); | |
| } | |
| public BadgeTabLayout(Context context, AttributeSet attrs) { | |
| super(context, attrs); | |
| } | |
| public BadgeTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { | |
| super(context, attrs, defStyleAttr); | |
| } | |
| public Builder with(int position) { | |
| Tab tab = getTabAt(position); | |
| return with(tab); | |
| } | |
| /** | |
| * Apply a builder for this tab. | |
| * | |
| * @param tab for which we create a new builder or retrieve its builder if existed. | |
| * @return the required Builder. | |
| */ | |
| public Builder with(Tab tab) { | |
| if (tab == null) { | |
| throw new IllegalArgumentException("Tab must not be null"); | |
| } | |
| Builder builder = mTabBuilders.get(tab.getPosition()); | |
| if (builder == null) { | |
| builder = new Builder(this, tab); | |
| mTabBuilders.put(tab.getPosition(), builder); | |
| } | |
| return builder; | |
| } | |
| public static final class Builder { | |
| /** | |
| * This badge widget must not support this value. | |
| */ | |
| private static final int INVALID_NUMBER = Integer.MIN_VALUE; | |
| @Nullable final View mView; | |
| final Context mContext; | |
| final TabLayout.Tab mTab; | |
| @Nullable TextView mBadgeTextView; | |
| @Nullable ImageView mIconView; | |
| Drawable mIconDrawable; | |
| Integer mIconColorFilter; | |
| int mBadgeCount = Integer.MIN_VALUE; | |
| boolean mHasBadge = false; | |
| /** | |
| * This construct take a TabLayout parent to have its context and other attributes sets. And | |
| * the tab whose icon will be updated. | |
| * | |
| * @param parent | |
| * @param tab | |
| */ | |
| private Builder(TabLayout parent, @NonNull TabLayout.Tab tab) { | |
| super(); | |
| this.mContext = parent.getContext(); | |
| this.mTab = tab; | |
| // initialize current tab's custom view. | |
| if (tab.getCustomView() != null) { | |
| this.mView = tab.getCustomView(); | |
| } else { | |
| this.mView = LayoutInflater.from(parent.getContext()) | |
| .inflate(R.layout.tab_custom_icon, parent, false); | |
| } | |
| if (mView != null) { | |
| this.mIconView = (ImageView) mView.findViewById(R.id.tab_icon); | |
| this.mBadgeTextView = (TextView) mView.findViewById(R.id.tab_badge); | |
| } | |
| if (this.mBadgeTextView != null) { | |
| this.mHasBadge = mBadgeTextView.getVisibility() == View.VISIBLE; | |
| try { | |
| this.mBadgeCount = Integer.parseInt(mBadgeTextView.getText().toString()); | |
| } catch (NumberFormatException er) { | |
| er.printStackTrace(); | |
| this.mBadgeCount = INVALID_NUMBER; | |
| } | |
| } | |
| if (this.mIconView != null) { | |
| mIconDrawable = mIconView.getDrawable(); | |
| } | |
| } | |
| /** | |
| * The related Tab is about to have a badge | |
| * | |
| * @return this builder | |
| */ | |
| public Builder hasBadge() { | |
| mHasBadge = true; | |
| return this; | |
| } | |
| /** | |
| * The related Tab is not about to have a badge | |
| * | |
| * @return this builder | |
| */ | |
| public Builder noBadge() { | |
| mHasBadge = false; | |
| return this; | |
| } | |
| /** | |
| * Dynamically set the availability of tab's badge | |
| * | |
| * @param hasBadge | |
| * @return this builder | |
| */ | |
| // This method is used for DEBUG purpose only | |
| /*hide*/ | |
| public Builder badge(boolean hasBadge) { | |
| mHasBadge = hasBadge; | |
| return this; | |
| } | |
| /** | |
| * Set icon custom drawable by Resource ID; | |
| * | |
| * @param drawableRes | |
| * @return this builder | |
| */ | |
| public Builder icon(int drawableRes) { | |
| mIconDrawable = getDrawableCompat(mContext, drawableRes); | |
| return this; | |
| } | |
| /** | |
| * Set icon custom drawable by Drawable Object | |
| * | |
| * @param drawable | |
| * @return this builder | |
| */ | |
| public Builder icon(Drawable drawable) { | |
| mIconDrawable = drawable; | |
| return this; | |
| } | |
| /** | |
| * Set drawable color. Use this when user wants to change drawable's color filter | |
| * | |
| * @param color | |
| * @return this builder | |
| */ | |
| public Builder iconColor(Integer color) { | |
| mIconColorFilter = color; | |
| return this; | |
| } | |
| /** | |
| * increase current badge by 1 | |
| * | |
| * @return this builder | |
| */ | |
| public Builder increase() { | |
| mBadgeCount = | |
| mBadgeTextView == null ? | |
| INVALID_NUMBER | |
| : | |
| Integer.parseInt(mBadgeTextView.getText().toString()) + 1; | |
| return this; | |
| } | |
| /** | |
| * decrease current badge by 1 | |
| * | |
| * @return | |
| */ | |
| public Builder decrease() { | |
| mBadgeCount = | |
| mBadgeTextView == null ? | |
| INVALID_NUMBER | |
| : | |
| Integer.parseInt(mBadgeTextView.getText().toString()) - 1; | |
| return this; | |
| } | |
| /** | |
| * set badge count | |
| * | |
| * @param count expected badge number | |
| * @return this builder | |
| */ | |
| public Builder badgeCount(int count) { | |
| mBadgeCount = count; | |
| return this; | |
| } | |
| /** | |
| * Build the current Tab icon's custom view | |
| */ | |
| public void build() { | |
| if (mView == null) { | |
| return; | |
| } | |
| // update badge counter | |
| if (mBadgeTextView != null) { | |
| mBadgeTextView.setText(formatBadgeNumber(mBadgeCount)); | |
| if (mHasBadge) { | |
| mBadgeTextView.setVisibility(View.VISIBLE); | |
| } else { | |
| // set to View#INVISIBLE to not screw up the layout | |
| mBadgeTextView.setVisibility(View.INVISIBLE); | |
| } | |
| } | |
| // update icon drawable | |
| if (mIconView != null && mIconDrawable != null) { | |
| mIconView.setImageDrawable(mIconDrawable.mutate()); | |
| // be careful if you modify this. make sure your result matches your expectation. | |
| if (mIconColorFilter != null) { | |
| mIconDrawable.setColorFilter(mIconColorFilter, PorterDuff.Mode.MULTIPLY); | |
| } | |
| } | |
| mTab.setCustomView(mView); | |
| } | |
| } | |
| private static Drawable getDrawableCompat(Context context, int drawableRes) { | |
| Drawable drawable = null; | |
| try { | |
| if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | |
| drawable = context.getResources().getDrawable(drawableRes); | |
| } else { | |
| drawable = context.getResources().getDrawable(drawableRes, context.getTheme()); | |
| } | |
| } catch (NullPointerException ex) { | |
| ex.printStackTrace(); | |
| } | |
| return drawable; | |
| } | |
| /** | |
| * This format must follow User's badge policy. | |
| * | |
| * @param value of current badge | |
| * @return corresponding badge number. TextView need to be passed by a String/CharSequence | |
| */ | |
| private static String formatBadgeNumber(int value) { | |
| if (value < 0) { | |
| return "-" + formatBadgeNumber(-value); | |
| } | |
| if (value <= 10) { | |
| // equivalent to String#valueOf(int); | |
| return Integer.toString(value); | |
| } | |
| // my own policy | |
| return "10+"; | |
| } | |
| } |
Hi creativetrendsapps,
I'm really new to android hence forgive my amature question.
could you perhaps give me a sample of how to use this gist. I'm looking to implement a shopping cart with a badge containing the number of items in the cart.
This is my mainActivity
package tk.nimzymaina.beta;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import java.util.ArrayList;
import java.util.List;
import butterknife.ButterKnife;
import butterknife.OnClick;
import tk.nimzymaina.beta.Events.CluckEvent;
import tk.nimzymaina.beta.fragments.OneFragment;
import tk.nimzymaina.beta.fragments.ThreeFragment;
import tk.nimzymaina.beta.fragments.TwoFragment;
import tk.nimzymaina.beta.infrustructure.EventBus;
public class MainActivity extends AppCompatActivity {
//protected Bus bus = new Bus();
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setOffscreenPageLimit(2);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
// @OnClick(R.id.btn)
// void Deme(){
// EventBus.getBus().post(new CluckEvent("Yahaya"));
// }
@Subscribe
public void Reciever(CluckEvent event){
Toast.makeText(getApplicationContext(),event.getMessage(),Toast.LENGTH_SHORT).show();
}
@Override
public void onResume(){
super.onResume();
EventBus.getBus().register(this);
}
@Override
public void onPause(){
super.onPause();
EventBus.getBus().unregister(this);
}
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new OneFragment(), "ONE");
adapter.addFragment(new TwoFragment(), "TWO");
adapter.addFragment(new ThreeFragment(), "THREE");
viewPager.setAdapter(adapter);
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = 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 addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
}
and this is my layout
<android.support.design.widget.CoordinatorLayout 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:fitsSystemWindows="true"
tools:context="tk.nimzymaina.beta.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways" />
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill"/>
</android.support.design.widget.AppBarLayout>
<!--<include layout="@layout/content_main" />-->
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
Thanks in advance
It seems that I get no notification about these issues above. I will try to read them and figure out some solution later ...
@eneim how can i implementing this class? could you please paste simple code to know about that? i cant implementing that and i get erro, please
@NimzyMaina could you implementing this feature and set badget for some tabs?
The java implementation could be:
yourBadgetTabLayoutObject.with(desireTabPosition).badge(true).badgeCount(1).build();
@eneim can you add this part of code to your gist? It adds content description attribute to mIconView for those Tabs as part of the builder.
/**
* set Content Description
*
* @param contentDescription expected string for content description.
* @return this builder
*/
public Builder imageContentDescription(String contentDescription) {
mIconView.setContentDescription(contentDescription);
return this;
}
}
how i can implement this view in XML?
@eneim how can i use text instead of icon in this?
@ludmilamm thanks how to add text instead of icon?
Is there an example of this code anywhere?