-
-
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?