Android 仿微信底部漸變Tab效果
先來看一下效果圖
除了第三個的發(fā)現(xiàn)Tab有所差別外,其他的基本還原了微信的底部Tab漸變效果
每個Tab都是一個自定義View,根據(jù)ImageView的tint屬性來實(shí)現(xiàn)顏色漸變效果,tint屬性的使用可以看我的上一篇文章
我將自定義View命名為ShadeView,包含四個自定義屬性
意思分別為圖標(biāo)、背景色、底部文本、底部文本大小
<declare-styleable name="ShadeView"> <attr name="icon" format="reference" /> <attr name="color" format="color" /> <attr name="text" format="string" /> <attr name="text_size" format="dimension" /> </declare-styleable>
ShadeView的定義如下,主要是進(jìn)行繪圖操作,并向外提供改變透明度和圖標(biāo)的方法
public class ShadeView extends View { /** * 圖標(biāo) */ private Bitmap iconBitmap; /** * 圖標(biāo)背景色 */ private int iconBackgroundColor; /** * 圖標(biāo)默認(rèn)背景色 */ private final int DEFAULT_ICON_BACKGROUND_COLOR = 0x3CAF36; /** * 圖標(biāo)底部文本 */ private String text; /** * 圖標(biāo)底部文字默認(rèn)大小(sp) */ private final int DEFAULT_TEXT_SIZE = 12; /** * 圖標(biāo)底部文字默認(rèn)顏色 */ private final int DEFAULT_TEXT_COLOR = 0x2B2B2B; /** * 圖標(biāo)繪制范圍 */ private Rect iconRect; /** * 文字筆畫 */ private Paint textPaint; /** * 文字范圍 */ private Rect textBound; /** * 透明度(0.0-1.0) */ private float mAlpha; private Bitmap mBitmap; public ShadeView(Context context, AttributeSet attrs) { super(context, attrs); //獲取自定義屬性值 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShadeView); BitmapDrawable drawable = (BitmapDrawable) typedArray.getDrawable(R.styleable.ShadeView_icon); if (drawable != null) { iconBitmap = drawable.getBitmap(); } iconBackgroundColor = typedArray.getColor(R.styleable.ShadeView_color, DEFAULT_ICON_BACKGROUND_COLOR); text = typedArray.getString(R.styleable.ShadeView_text); int textSize = (int) typedArray.getDimension(R.styleable.ShadeView_text_size, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE, getResources().getDisplayMetrics())); //資源回收 typedArray.recycle(); //初始化 textBound = new Rect(); textPaint = new Paint(); textPaint.setTextSize(textSize); textPaint.setColor(DEFAULT_TEXT_COLOR); textPaint.setAntiAlias(true); textPaint.setDither(true); textPaint.getTextBounds(text, 0, text.length(), textBound); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //因?yàn)閳D標(biāo)是正方形且需要居中顯示的,所以View的大小去掉padding和文字所占空間后, //剩余的空間的寬和高的最小值才是圖標(biāo)的邊長 int bitmapSide = Math.min(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - textBound.height()); int left = getMeasuredWidth() / 2 - bitmapSide / 2; int top = (getMeasuredHeight() - textBound.height()) / 2 - bitmapSide / 2; //獲取圖標(biāo)的繪制范圍 iconRect = new Rect(left, top, left + bitmapSide, top + bitmapSide); } @Override protected void onDraw(Canvas canvas) { //進(jìn)一取整 int alpha = (int) Math.ceil((255 * mAlpha)); //繪制原圖標(biāo) canvas.drawBitmap(iconBitmap, null, iconRect, null); setupTargetBitmap(alpha); drawSourceText(canvas, alpha); drawTargetText(canvas, alpha); canvas.drawBitmap(mBitmap, 0, 0, null); } /** * 在mBitmap上繪制以iconBackgroundColor顏色為Dst,DST_IN模式下的圖標(biāo) * * @param alpha Src顏色的透明度 */ private void setupTargetBitmap(int alpha) { mBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(mBitmap); Paint paint = new Paint(); paint.setColor(iconBackgroundColor); paint.setAntiAlias(true); paint.setDither(true); paint.setAlpha(alpha); //在圖標(biāo)背后先繪制一層iconBackgroundColor顏色的背景 canvas.drawRect(iconRect, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); paint.setAlpha(255); //在mBitmap上繪制以iconBackgroundColor顏色為Dst,DST_IN模式下的圖標(biāo) canvas.drawBitmap(iconBitmap, null, iconRect, paint); } /** * 繪制默認(rèn)狀態(tài)下的字體 * * @param canvas Canvas * @param alpha 字體顏色透明度 */ private void drawSourceText(Canvas canvas, int alpha) { textPaint.setColor(DEFAULT_TEXT_COLOR); textPaint.setAlpha(255 - alpha); canvas.drawText(text, iconRect.left + iconRect.width() / 2 - textBound.width() / 2, iconRect.bottom + textBound.height(), textPaint); } /** * 繪制滑動到該標(biāo)簽時的字體 * * @param canvas Canvas * @param alpha 字體顏色透明度 */ private void drawTargetText(Canvas canvas, int alpha) { textPaint.setColor(iconBackgroundColor); textPaint.setAlpha(alpha); canvas.drawText(text, iconRect.left + iconRect.width() / 2 - textBound.width() / 2, iconRect.bottom + textBound.height(), textPaint); } /** * 設(shè)置圖標(biāo)透明度并重繪 * * @param alpha 透明度 */ public void setIconAlpha(float alpha) { if (mAlpha != alpha) { this.mAlpha = alpha; invalidateView(); } } public void setIconBitmap(Context context, int resourceID) { BitmapDrawable bitmapDrawable = (BitmapDrawable) ContextCompat.getDrawable(context, resourceID); if (!bitmapDrawable.getBitmap().equals(iconBitmap)) { iconBitmap = bitmapDrawable.getBitmap(); invalidateView(); } } /** * 判斷當(dāng)前是否為UI線程,是則直接重繪,否則調(diào)用postInvalidate()利用Handler來重繪 */ private void invalidateView() { if (Looper.getMainLooper() == Looper.myLooper()) { invalidate(); } else { postInvalidate(); } } private static final String STATE_INSTANCE = "STATE_INSTANCE"; private static final String STATE_ALPHA = "STATE_ALPHA"; /** * 保存狀態(tài) * * @return Parcelable */ @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(STATE_INSTANCE, super.onSaveInstanceState()); bundle.putFloat(STATE_ALPHA, mAlpha); return bundle; } /** * 恢復(fù)狀態(tài) * * @param parcelable Parcelable */ @Override protected void onRestoreInstanceState(Parcelable parcelable) { if (parcelable instanceof Bundle) { Bundle bundle = (Bundle) parcelable; mAlpha = bundle.getFloat(STATE_ALPHA); super.onRestoreInstanceState(bundle.getParcelable(STATE_INSTANCE)); } else { super.onRestoreInstanceState(parcelable); } } }
然后在布局文件中聲明使用,這里不需要每個自定義屬性都使用到,因?yàn)槲乙蔡峁┝四J(rèn)值
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v4.view.ViewPager android:id="@+id/id_viewpager" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:layout_width="match_parent" android:layout_height="60dp" android:background="@drawable/tab_background" android:orientation="horizontal" android:paddingBottom="3dp" android:paddingTop="1dp"> <com.example.zy.myapplication.ShadeView android:id="@+id/id_indicator_one" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:padding="5dp" app:icon="@drawable/weixin" app:text="微信" /> <com.example.zy.myapplication.ShadeView android:id="@+id/id_indicator_two" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:padding="5dp" app:icon="@drawable/address_book" app:text="通訊錄" /> <com.example.zy.myapplication.ShadeView android:id="@+id/id_indicator_three" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:padding="5dp" app:icon="@drawable/discover" app:text="發(fā)現(xiàn)" /> <com.example.zy.myapplication.ShadeView android:id="@+id/id_indicator_four" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:padding="5dp" app:icon="@drawable/me" app:text="我" /> </LinearLayout> </LinearLayout>
因?yàn)橹鹘缑媸荲iewPager,這里就需要一個Fragment子類
public class TabFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { String mTitle = "微信"; if (getArguments() != null) { mTitle = getArguments().getString("Title", "微信"); } TextView textView = new TextView(getActivity()); textView.setTextSize(25); textView.setGravity(Gravity.CENTER); textView.setText(mTitle); return textView; } }
MainActivity代碼如下,重點(diǎn)是對viewPager進(jìn)行滑動監(jiān)聽,根據(jù)滑動偏移量來動態(tài)改變透明度alpha,從而實(shí)現(xiàn)顏色漸變效果
public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener, View.OnClickListener { private List<TabFragment> tabFragments; private List<ShadeView> tabIndicators; private ViewPager viewPager; private FragmentPagerAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); viewPager = (ViewPager) findViewById(R.id.id_viewpager); viewPager.setAdapter(adapter); viewPager.addOnPageChangeListener(this); } private void initData() { tabFragments = new ArrayList<>(); tabIndicators = new ArrayList<>(); String[] titles = new String[]{"微信", "通訊錄", "發(fā)現(xiàn)", "我"}; for (String title : titles) { TabFragment tabFragment = new TabFragment(); Bundle bundle = new Bundle(); bundle.putString("Title", title); tabFragment.setArguments(bundle); tabFragments.add(tabFragment); } adapter = new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public int getCount() { return tabFragments.size(); } @Override public Fragment getItem(int arg0) { return tabFragments.get(arg0); } }; initTabIndicator(); } private void initTabIndicator() { ShadeView one = (ShadeView) findViewById(R.id.id_indicator_one); ShadeView two = (ShadeView) findViewById(R.id.id_indicator_two); ShadeView three = (ShadeView) findViewById(R.id.id_indicator_three); ShadeView four = (ShadeView) findViewById(R.id.id_indicator_four); tabIndicators.add(one); tabIndicators.add(two); tabIndicators.add(three); tabIndicators.add(four); one.setOnClickListener(this); two.setOnClickListener(this); three.setOnClickListener(this); four.setOnClickListener(this); one.setIconAlpha(1.0f); } @Override public void onClick(View v) { resetTabsStatus(); switch (v.getId()) { case R.id.id_indicator_one: tabIndicators.get(0).setIconAlpha(1.0f); viewPager.setCurrentItem(0, false); break; case R.id.id_indicator_two: tabIndicators.get(1).setIconAlpha(1.0f); viewPager.setCurrentItem(1, false); break; case R.id.id_indicator_three: tabIndicators.get(2).setIconAlpha(1.0f); viewPager.setCurrentItem(2, false); break; case R.id.id_indicator_four: tabIndicators.get(3).setIconAlpha(1.0f); viewPager.setCurrentItem(3, false); break; } } /** * 重置Tab狀態(tài) */ private void resetTabsStatus() { for (int i = 0; i < tabIndicators.size(); i++) { tabIndicators.get(i).setIconAlpha(0); } } /** * 如果是直接點(diǎn)擊圖標(biāo)來跳轉(zhuǎn)頁面的話,position值為0到3,positionOffset一直為0.0 * 如果是通過滑動來跳轉(zhuǎn)頁面的話 * 假如是從第一頁滑動到第二頁 * 在這個過程中,positionOffset從接近0逐漸增大到接近1.0,滑動完成后又恢復(fù)到0.0,而position只有在滑動完成后才從0變?yōu)? * 假如是從第二頁滑動到第一頁 * 在這個過程中,positionOffset從接近1.0逐漸減小到0.0,而position一直是0 * * @param position 當(dāng)前頁面索引 * @param positionOffset 偏移量 * @param positionOffsetPixels 偏移量 */ @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { Log.e("TAG", "position==" + position); Log.e("TAG", "positionOffset==" + positionOffset); Log.e("TAG", "positionOffsetPixels==" + positionOffsetPixels); if (positionOffset > 0) { ShadeView leftTab = tabIndicators.get(position); ShadeView rightTab = tabIndicators.get(position + 1); leftTab.setIconAlpha(1 - positionOffset); rightTab.setIconAlpha(positionOffset); } } @Override public void onPageSelected(int position) { if (position == 2) { tabIndicators.get(position).setIconBitmap(this, R.drawable.discover_green); } else { tabIndicators.get(2).setIconBitmap(this, R.drawable.discover); } } @Override public void onPageScrollStateChanged(int state) { } }
總結(jié)
以上所述是小編給大家介紹的Android 仿微信底部漸變Tab效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- Android 使用FragmentTabHost實(shí)現(xiàn)底部菜單功能
- Android中修改TabLayout底部導(dǎo)航條Indicator長短的方法
- Android TabWidget底部顯示效果
- Android編程實(shí)現(xiàn)將tab選項(xiàng)卡放在屏幕底部的方法
- Android中TabLayout+ViewPager 簡單實(shí)現(xiàn)app底部Tab導(dǎo)航欄
- Android design包自定義tablayout的底部導(dǎo)航欄的實(shí)現(xiàn)方法
- 關(guān)注Ionic底部導(dǎo)航按鈕tabs在android情況下浮在上面的處理
- Android仿微信底部實(shí)現(xiàn)Tab選項(xiàng)卡切換效果
- android 選項(xiàng)卡(TabHost)如何放置在屏幕的底部
- Android TabLayout 實(shí)現(xiàn)底部Tab的示例代碼
相關(guān)文章
Android編程實(shí)現(xiàn)讀取工程中的txt文件功能
這篇文章主要介紹了Android編程實(shí)現(xiàn)讀取工程中的txt文件功能,結(jié)合實(shí)例形式詳細(xì)分析了Android讀取txt文件的原理、操作步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-02-02Android?OkHttp庫簡單使用和封裝教程助你快速掌握網(wǎng)絡(luò)請求技能
OkHttp是一個高效的HTTP客戶端庫,適用于Android和Java應(yīng)用程序。它支持HTTP/2和SPDY協(xié)議,提供了同步和異步請求API、請求和響應(yīng)攔截器、連接池和多路復(fù)用器、緩存支持、GZIP和DEFLATE壓縮等功能,可以大大提高網(wǎng)絡(luò)請求的性能和可擴(kuò)展性2023-04-04Android開發(fā)自定義雙向SeekBar拖動條控件
這篇文章主要為大家介紹了Android開發(fā)自定義雙向SeekBar拖動條控件使用實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android序列化實(shí)現(xiàn)接口Serializable與Parcelable詳解
我們使用 Intent 傳遞數(shù)據(jù)的時候,putExtra() 所支持的數(shù)據(jù)類型事有限的,當(dāng)需要傳遞自定義對象的時候就需要序列化。Serializable更簡單但是會把整個對象進(jìn)行序列化因此效率比Parcelable低一些2022-12-12Android自定義View實(shí)現(xiàn)黑客帝國數(shù)字雨效果
這篇文章主要給大家介紹了關(guān)于Android自定義View實(shí)現(xiàn)黑客帝國數(shù)字雨效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08Android 權(quán)限(permission)整理
本文主要介紹Android 權(quán)限的整理,在開發(fā)Android應(yīng)用的時候,根據(jù)需求的不同,會用到不同的權(quán)限,這里整理了很多,有需要的同學(xué)可以參考下2016-07-07Android編程實(shí)現(xiàn)獲取系統(tǒng)內(nèi)存、CPU使用率及狀態(tài)欄高度的方法示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)獲取系統(tǒng)內(nèi)存、CPU使用率及狀態(tài)欄高度的方法,涉及Android基于自定義類實(shí)現(xiàn)針對系統(tǒng)硬件信息的相關(guān)獲取操作技巧,需要的朋友可以參考下2017-08-08Android RecyclerView上拉加載和下拉刷新(基礎(chǔ)版)
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView上拉加載和下拉刷新的相實(shí)現(xiàn)方法,內(nèi)容簡單,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02