仿微信透明度渐变且带红点提醒的底部栏

         微信主页面慢慢水平滑动时,会发现底部栏的图标以及文字有透明度渐变效果。其中,透明度的大小依赖ViewPagerOffset系数。从当前页面切换到下一个页面,当前页面的底部栏颜色会逐渐变淡,而下一个页面的底部栏颜色会逐渐变深。其实底部栏渐变效果已经有其他人写过博客以及发表过开源项目,但是觉得还是缺少点什么。打开微信看看,噢耶,是红点提醒和未读统计。既然有完善空间,那么说做就做。效果图如下:


       其实,抛开透明度渐变效果,RadioGroup和RadioButton就可以实现页面切换。顺着这个思路,自定义控件分别继承RadioGroup和RadioButton,根据ViewPager切换页面的偏移值Offset系数,改变RadioButton的Alpha透明度数值,这样就可实现渐变效果。

//根据偏移值更新透明度
private void updateOffsetAlpha(int position, float positionOffset){
    ((AlphaRadioButton)getChildAt(position)).updateAlpha(255 * (1 - positionOffset));
    ((AlphaRadioButton)getChildAt(position + 1)).updateAlpha(255 * positionOffset);
}
//ViewPager监听positionOffset
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    if(positionOffset > 0)//offset发生变化,根据offset改变child的alpha
        updateOffsetAlpha(position, positionOffset);
}
      这里遍历所有RadioButton,调用对应的RadioButton去改变透明度,当前页面Alpha=255 * (1 - positionOffset),将要切换到的页面Alpha=255 * positionOffset。每次改变透明度都触发onDraw方法,在里面重新绘制图标与文字。

//根据alpha值改变当前页面的底部栏文字透明度
private void drawUnSelectedText(Canvas canvas) {
    mTextPaint.setColor(getCurrentTextColor());
    mTextPaint.setAlpha(255 - mAlpha);
    canvas.drawText(getText().toString(), getWidth() / 2 - mTextWidth / 2
            , mIconHeight + getPaddingTop() + mFontHeight, mTextPaint);
}
//根据alpha值改变下一个页面的底部栏文字透明度
private void drawSelectedText(Canvas canvas) {
    mTextPaint.setColor(mColor);
    mTextPaint.setAlpha(mAlpha);
    canvas.drawText(getText().toString(), getWidth() / 2 - mTextWidth / 2
            , mIconHeight + getPaddingTop() + mFontHeight, mTextPaint);
}
      在xml布局文件,引入自定义控件,并设置选中图标、未选中图标、选中的颜色、图标宽度与高度。如下所以:

<com.frank.bottombar.AlphaRadioGroup
    android:id="@+id/radio_group"
    android:layout_width="match_parent"
    android:layout_height="@dimen/bottom_bar_size"
    android:layout_alignParentBottom="true"
    android:orientation="horizontal">

    <com.frank.bottombar.AlphaRadioButton
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:checked="true"
        android:gravity="center"
        android:paddingTop="3dp"
        android:text="@string/message"
        android:textColor="@color/light_gray"
        android:textSize="13sp"
        app:icon_unselected="@drawable/message"
        app:color_selected="@color/green"
        app:icon_selected="@drawable/message_green"
        app:icon_height_default="25dp"
        app:icon_width_default="25dp" />

    <com.frank.bottombar.AlphaRadioButton
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:paddingTop="3dp"
        android:text="@string/contact"
        android:textColor="@color/light_gray"
        android:textSize="13sp"
        app:icon_unselected="@drawable/contact"
        app:color_selected="@color/green"
        app:icon_selected="@drawable/contact_green"
        app:icon_height_default="25dp"
        app:icon_width_default="25dp" />

    <com.frank.bottombar.AlphaRadioButton
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:paddingTop="3dp"
        android:text="@string/discover"
        android:textColor="@color/light_gray"
        android:textSize="13sp"
        app:icon_unselected="@drawable/discover"
        app:color_selected="@color/green"
        app:icon_selected="@drawable/discover_green"
        app:icon_height_default="25dp"
        app:icon_width_default="25dp" />

    <com.frank.bottombar.AlphaRadioButton
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:paddingTop="3dp"
        android:text="@string/about_me"
        android:textColor="@color/light_gray"
        android:textSize="13sp"
        app:icon_unselected="@drawable/about_me"
        app:color_selected="@color/green"
        app:icon_selected="@drawable/about_me_green"
        app:icon_height_default="25dp"
        app:icon_width_default="25dp" />
</com.frank.bottombar.AlphaRadioGroup>
当然,这些自定义属性需要在attrs.xml文件中声明:

<declare-styleable name="BottomBar">
    <attr name="icon_selected" format="reference"></attr>
    <attr name="icon_unselected" format="reference"></attr>
    <attr name="color_selected" format="color"></attr>
    <attr name="icon_width_default" format="dimension"></attr>
    <attr name="icon_height_default" format="dimension"></attr>
</declare-styleable>
       接下来是红点提醒与未读数统计。它们只要调用一句代码,参入需要显示的position、count就可以。当然,不可能只有显示,永远显示那就成为无解bug了。同样地,调用一句代码就可以把显示的红点或未读统计隐藏。这里开子线程,只是为了测试是否可以更在子线程调用以下方法。

alphaRadioGroup.showDot(1);//显示红点
alphaRadioGroup.showUnreadCount(0, 9);//显示未读数量统计
new Thread(new Runnable() {
    @Override
    public void run() {
        SystemClock.sleep(2000);
        alphaRadioGroup.hideUnreadCount(0);//隐藏未读数量统计
        SystemClock.sleep(2000);
        alphaRadioGroup.hideDot(1);//隐藏红点
    }
}).start();

       理论上,只能在主线程直接操作UI界面的,也就是调用invalidate方法。在这里考虑到子线程也可能会调用,所以调用方法前加了当前线程判断,如果是子线程,改为调用postInvalidate方法。

// 显示红点
public void showDot(int position){
    checkPosition(position);
    ((AlphaRadioButton)getChildAt(position)).showDot(true);
}

//隐藏红点
public void hideDot(int position){
    checkPosition(position);
    ((AlphaRadioButton)getChildAt(position)).showDot(false);
}

// 显示未读数量
public void showUnreadCount(int position, int count){
    checkPosition(position);
    ((AlphaRadioButton)getChildAt(position)).showUnreadCount(true, count);
}

// 隐藏未读数量
public void hideUnreadCount(int position){
    checkPosition(position);
    ((AlphaRadioButton)getChildAt(position)).showUnreadCount(false, 0);
}

// 检查绘制的position合法性
protected void checkPosition(int position){
    if(position < 0 || position >= getChildCount())
        throw new IndexOutOfBoundsException("The position to show dot should not be smaller than 0 " +
                "or greater than childCount");
}
       上面方法是调用RadioButton的更新页面,被调用方法如下:

//是否显示红点
public void showDot(boolean drawDot){
    this.drawDot = drawDot;
    if(isMainThread())
        invalidate();
    else
        postInvalidate();
}

//是否显示未读数量
public void showUnreadCount(boolean drawCount, int count){
    this.drawCount = drawCount;
    this.count = count;
    if(isMainThread())
        invalidate();
    else
        postInvalidate();
}
       在onDraw方法里,具体实现绘制页面,显示红点或者未读统计的执行代码如下:

//在图标右上角画红点
public void drawDot(Canvas canvas){
    mDotPaint.setColor(Color.RED);
    canvas.drawCircle((getWidth() + mIconWidth)/2, getPaddingTop() + 10, dp2px(4), mDotPaint);
}

//在图标右上角画未读数量
public void drawCount(Canvas canvas, int count){
    mDotPaint.setColor(Color.RED);
    mCountPaint.setColor(Color.WHITE);

    if(count < 10){//1-9
        mCountPaint.setTextSize(sp2px(12));
        canvas.drawCircle((getWidth() + mIconWidth)/2, getPaddingTop() + dp2px(7), dp2px(9), mDotPaint);
        canvas.drawText(String.valueOf(count), (getWidth() + mIconWidth)/2 - dp2px(3),
            getPaddingTop() + dp2px(11), mCountPaint);
    }else if(count < 100){//10-100
        mCountPaint.setTextSize(sp2px(10));
        canvas.drawCircle((getWidth() + mIconWidth)/2, getPaddingTop() + dp2px(7), dp2px(9), mDotPaint);
        canvas.drawText(String.valueOf(count), (getWidth() + mIconWidth)/2 - dp2px(6),
            getPaddingTop() + dp2px(10), mCountPaint);
    }else {//大于100
        mCountPaint.setTextSize(sp2px(8));
        canvas.drawCircle((getWidth() + mIconWidth)/2, getPaddingTop() + dp2px(7), dp2px(9), mDotPaint);
        canvas.drawText("99+", (getWidth() + mIconWidth)/2 - dp2px(6),
            getPaddingTop() + dp2px(11), mCountPaint);
    }
}
       可以看到,绘制红点提醒相对简单。但是绘制未读数量时,考虑到圆圈背景大小不变,而不同位数的数字所占的位置不同,这里分三种情况:1-9,10-99和99以上。两位数的字体尺寸需要改小一点,99条以上统一显示"99+"。另外,考虑到不同分辨率手机适配问题,使用到dp2px和sp2px方法进行单位转换。

       好了,透明度渐变效果加上红点提醒与未读统计介绍完了。如果大家有什么问题,欢迎交流,同样也欢迎分享其它效果。






评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐福记456

您的鼓励和肯定是我创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值