-
-
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+"; | |
} | |
} |
how can i have text instead icon under Badged?
Are you able to include the definitions of:
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:layout_width="@dimen/tab_width"
android:layout_height="@dimen/tab_height"
or maybe even how to use this class at all?
Hi Eneim, I was wondering if I could use some of your solution, as I wanted a functionality something like :
I have a scrollable tab and at any particular time the selected tab will be in center and that tab should have two arrows, one poiting from top and one pointing from down, to give it a fvisual eel that its currently selected.Any points as to how to use it, I am making a xamarin Android app. Thank you, if you want I can show you the image.
Is there an example of this code anywhere?
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?
Badge background (red circle)
icon_badge.xml