先来看一下效果图

除了第三个的发现Tab有所差别外,其他的基本还原了微信的底部Tab渐变效果

每个Tab都是一个自定义View,根据ImageView的tint属性来实现颜色渐变效果,tint属性的使用可以看我的上一篇文章

我将自定义View命名为ShadeView,包含四个自定义属性

意思分别为图标、背景色、底部文本、底部文本大小

ShadeView的定义如下,主要是进行绘图操作,并向外提供改变透明度和图标的方法

public class ShadeView extends View {

/**

* 图标

*/

private Bitmap iconBitmap;

/**

* 图标背景色

*/

private int iconBackgroundColor;

/**

* 图标默认背景色

*/

private final int DEFAULT_ICON_BACKGROUND_COLOR = 0x3CAF36;

/**

* 图标底部文本

*/

private String text;

/**

* 图标底部文字默认大小(sp)

*/

private final int DEFAULT_TEXT_SIZE = 12;

/**

* 图标底部文字默认颜色

*/

private final int DEFAULT_TEXT_COLOR = 0x2B2B2B;

/**

* 图标绘制范围

*/

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);

//因为图标是正方形且需要居中显示的,所以View的大小去掉padding和文字所占空间后,

//剩余的空间的宽和高的最小值才是图标的边长

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;

//获取图标的绘制范围

iconRect = new Rect(left, top, left + bitmapSide, top + bitmapSide);

}

@Override

protected void onDraw(Canvas canvas) {

//进一取整

int alpha = (int) Math.ceil((255 * mAlpha));

//绘制原图标

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模式下的图标

*

* @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);

//在图标背后先绘制一层iconBackgroundColor颜色的背景

canvas.drawRect(iconRect, paint);

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

paint.setAlpha(255);

//在mBitmap上绘制以iconBackgroundColor颜色为Dst,DST_IN模式下的图标

canvas.drawBitmap(iconBitmap, null, iconRect, paint);

}

/**

* 绘制默认状态下的字体

*

* @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);

}

/**

* 绘制滑动到该标签时的字体

*

* @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);

}

/**

* 设置图标透明度并重绘

*

* @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();

}

}

/**

* 判断当前是否为UI线程,是则直接重绘,否则调用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";

/**

* 保存状态

*

* @return Parcelable

*/

@Override

protected Parcelable onSaveInstanceState() {

Bundle bundle = new Bundle();

bundle.putParcelable(STATE_INSTANCE, super.onSaveInstanceState());

bundle.putFloat(STATE_ALPHA, mAlpha);

return bundle;

}

/**

* 恢复状态

*

* @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);

}

}

}

然后在布局文件中声明使用,这里不需要每个自定义属性都使用到,因为我也提供了默认值

xmlns:app="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:id="@+id/id_viewpager"

android:layout_width="match_parent"

android:layout_height="0dp"

android:layout_weight="1" />

android:layout_width="match_parent"

android:layout_height="60dp"

android:background="@drawable/tab_background"

android:orientation="horizontal"

android:paddingBottom="3dp"

android:paddingTop="1dp">

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="微信" />

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="通讯录" />

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="发现" />

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="我" />

因为主界面是ViewPager,这里就需要一个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代码如下,重点是对viewPager进行滑动监听,根据滑动偏移量来动态改变透明度alpha,从而实现颜色渐变效果

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener, View.OnClickListener {

private List tabFragments;

private List 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[]{"微信", "通讯录", "发现", "我"};

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状态

*/

private void resetTabsStatus() {

for (int i = 0; i < tabIndicators.size(); i++) {

tabIndicators.get(i).setIconAlpha(0);

}

}

/**

* 如果是直接点击图标来跳转页面的话,position值为0到3,positionOffset一直为0.0

* 如果是通过滑动来跳转页面的话

* 假如是从第一页滑动到第二页

* 在这个过程中,positionOffset从接近0逐渐增大到接近1.0,滑动完成后又恢复到0.0,而position只有在滑动完成后才从0变为1

* 假如是从第二页滑动到第一页

* 在这个过程中,positionOffset从接近1.0逐渐减小到0.0,而position一直是0

*

* @param position 当前页面索引

* @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) {

}

}

总结

以上所述是小编给大家介绍的Android 仿微信底部渐变Tab效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

android 底部tab效果,Android 仿微信底部渐变Tab效果相关推荐

  1. Android仿微信底部菜单栏+今日头条顶部导航栏

    背景 Android应用几乎都会用到底部菜单栏,在Material Design还没有出来之前,TabHost等技术一直占主流,现在Google新sdk中提供了TabLayout类可以便捷的做出底部菜 ...

  2. JFTabBar android强大的底部导航栏框架 (微信底部导航栏效果)

    TabBar这个名字相信很多学过一点IOS程序员都知道它是用来干嘛的,但本人也并非擅长开发IOS程序员,只是略懂略懂....这是一个很强大的TabBar,可满足很多需求.用起来也非常简单,在oncre ...

  3. android bmob 朋友圈,仿微信朋友圈视频效果 – MVideo

    MVideo 仿微信朋友圈视频效果,可以拖拽及缩放,视频查看,基于ijkplayer. Demo 入门 Step 1:在buil文件中添加JitPack仓库: allprojects { reposi ...

  4. Android 二维码扫描(仿微信界面),根据Google zxing

    Android 二维码扫描(仿微信界面),根据Google zxing Android项目开发中经常会用到二维码扫描,例如登陆.支付等谷歌方面已经有了一个开源库(地址: https://github. ...

  5. Android 使用 CameraX 快速实现仿微信短视频录制

    Android 使用 CameraX 快速实现仿微信短视频录制(轻触拍照.长按录像) https://github.com/ldlywt/MyCameraX 微信短视频android端 https:/ ...

  6. php仿微信底部菜单,Android实现简单底部导航栏 Android仿微信滑动切换效果

    Android仿微信滑动切换最终实现效果: 大体思路: 1. 主要使用两个自定义View配合实现; 底部图标加文字为一个自定义view,底部导航栏为一个载体,根据需要来添加底部图标; 2. 底部导航栏 ...

  7. Android仿微信底部菜单栏+顶部菜单栏(附源码)

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 本文要实现仿微信微信底部菜单栏+顶部菜单栏,采用ViewPage来做,每一个page对应一个X ...

  8. android高仿微信底部渐变导航栏

    最近有很多人问微信底部的变色卡片导航是怎么做的,我在网上看了好几个例子,都是效果接近,都存有一些差异,自己琢磨也做了一个,几乎99%的还原,效果还不错吧 仔细观察微信图片,发现他有两部分内容,外面的边 ...

  9. android 微信底部渐变,仿微信底部导航栏BottomNavigation渐变色

    ezgif.com-video-to-gif.gif 不说废话了,直接上代码: 自定义View /** * * author : Harvey * time : 2018/06/14 * desc : ...

最新文章

  1. Java编程入门(2.1):基础Java应用程序
  2. 【PP操作手册】工作中心的维护
  3. String对象的indexOf方法
  4. python的for语句有几种写法_Python if 和 for 的多种写法
  5. php验证百度云doc,百度云推送通知埋的大大的坑,成功测试REST API for PHP服务端...
  6. docker jib_Jib –为Spring Boot应用程序构建docker映像
  7. 三维网格精简算法java版_几种常见算法的精简版-
  8. 【方案分享】华为智慧农业解决方案.pdf(附下载链接)
  9. storm能不能测试wadl_测试网红燃油宝到底能不能除积碳,看完不花冤枉钱
  10. JavaScript就这么回事 (JS基础知识整理)
  11. c语言 sprintf_s 参数 通配符,Rust教程(翻译).doc
  12. HTML5 input新增属性
  13. 【UWA 学堂】部分渲染课程的调价通知
  14. 尚学堂马士兵Linux视频教程笔记
  15. sqliteman安装错误
  16. python制作动态二维码
  17. host文件的用途和用法
  18. linux 天文软件,Stellarium 0.18.0虚拟天文馆软件发布,支持HiPS
  19. Linux中pkg-config的使用
  20. ArcGIS Pro脚本工具(6)——修复CAD图层数据源

热门文章

  1. C++ 为什么要用覆盖(学习笔记)
  2. 面向对象的软件开发方法简介
  3. Centos7 安装配置 tomcat 并设置自动启动
  4. 推荐一款Python编辑器,集Pycharm和Sublime优点于一身的王者
  5. Docker快速搭建Tuleap项目管理平台
  6. 思科命令 service password-encryption
  7. 你还在用 Date?建议使用 LocalDateTime 了!
  8. 【C语言】输出半径1到10的圆的面积,当面积值超过100时,停止执行本程序
  9. MarkDown总结(适合初学者快速入门)
  10. C#算法设计查找篇之02-二分查找