渐变色仪表盘的实现(带初始化动画和多次改变动画始终点)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了渐变色仪表盘的实现(带初始化动画和多次改变动画始终点)相关的知识,希望对你有一定的参考价值。
参考技术A 好。需求已经出来了。那么就只能看看这个view要分成几个部分才能够更简单的做。终究胳膊扭不过大腿。首先是最外面的边框是虚线并且渐变色。里面的实线也是渐变色的。
然后就是中间的虚线长短不一,颜色不一。中间的字体是白色和另外一种颜色,大小不一。
最后的一点就是红色的指针。并且指针也要有动画,指针最后的位置要指向环形进度条的位置。
1.画环形渐变色的虚线
2.画内环渐变色实线
3.画中间不同颜色的弧度,这里采用了两种虚线相间的方式。具体实现方式就是距离不一 颜色不一
4.然后就是指针箭头的绘制。这里因为当时时间很紧,就没有用canvas去画,直接让UI设计师给切了一张指针图。
就是这样的指针图,因为指针要根据环形进度条的位置改变。所以我们要对这张图进行旋转操作。但是bitmap的旋转是按照中心点旋转的。所以我让UI设计师将指针切了上面看上去那么长。一半是指针,另外一半是空白。
首先是先加载指针图片。
然后根据环形进度条的位置进行旋转,那么我这里就用到了Matrix矩阵对bitmap进行了旋转。
现在其实就已经差不多了。但是还有一个很重要的点。因为要有从0到100的动画,还要环形进度条的颜色是渐变色。所以我根据属性动画存储进度条每次增长的位置,然后取两种颜色中的某一点过渡色。具体取过渡色的方法如下。
然后view中的两个文字我就不贴出来了。有需要的q我就行。其实到这view已经差不多了。主要是动画的起始。这里就不贴出了。源码供上。有什么写的不好的地方,还请各位大神多多指教!
渐变色仪表盘
微信底部滑动时图标渐变色的实现
周末想实现一下微信底部的渐变图案,折腾了一波,效果总算得上是差强人意。
下面是QQ的微信图标
首先想到两个方案就是设置背景透明度,和属性动画。但效果都被否决了,属性动画效果逼真一些,但是顶多算是B货。要实现高仿的A货,尽管不喜欢用还是得自定义View了。
动画折腾了好久尽管效果很接近,但是效果还是有所区别。但是自从看了微信的图片资源后,立马就反应过来了,原来这玩意是这样设计的。
利用两张同样大小的图片,一张是透明的,一张是填充色,这个效果就是把两张图片重叠起来,然后不断改变填充色的alpha透明度就实现了。原本以为很高深的技术,其实就是用的两张图片重叠混合而成的。原理知道了,接下来就是代码实现了,标准的自定义view流程。
首先在values文件夹下面新建attrs目录,接着就是自定义需要用到的属性了。
<resources>
<declare-styleable name="MyView">
<attr name="bottomBitmap format="reference" />
<attr name="topBitmap" format="reference" />
<attr name="text" format="string" />
<attr name="textsize" format="dimension" />
</declare-styleable>
</resources>
以上代码分别定义了,底部图片,上部图片,文字,和文字大小属性
接下来就是自定义View的实现,新建一个class类MyView继承View。
public MyView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
//从xml定义属性中拿到对应数值
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyView);
int indexCount = ta.getIndexCount();
for (int i = 0; i < indexCount; i++)
int index = ta.getIndex(i);
switch (index)
case R.styleable.MyView_text:
mText = ta.getString(index);
break;
case R.styleable.MyView_textsize:
mTextSize = ta.getDimension(index, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
break;
case R.styleable.MyView_bottomBitmap:
BitmapDrawable bottomDrawable = (BitmapDrawable) ta.getDrawable(index);
mBottomBitmap = bottomDrawable.getBitmap().copy(Bitmap.Config.ARGB_8888, true);
break;
case R.styleable.MyView_topBitmap:
BitmapDrawable topDrawable = (BitmapDrawable) ta.getDrawable(index);
mTopBitmap = topDrawable.getBitmap();
break;
//拿到底图的宽高
mBitmapWidth = mBottomBitmap.getWidth();
mBitmapHeight = mBottomBitmap.getHeight();
//初始化字体画笔
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextSize(mTextSize);
mTextBounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
mTextPaint.setTextSize(mTextSize);
//初始化底图和覆盖图画笔
mBottomBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTopBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
以上为MyView的构造方法,其中利用TypedArray拿到自定义的属性值,并且通过switch语句分别赋值给成员变量。之后是拿到底图的宽高,然后分别初始化字体画笔,底图和覆盖图画笔。
接下来就是view的测量:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//拿到测量宽高
int width = getMeasuredWidth();
int height = getMeasuredHeight();
//初始化bitmap的绘制区域
mBitmapRect = new Rect(width / 2 - mBitmapWidth / 2, height / 2 - mBitmapHeight / 2 - mTextBounds.height() / 2,
width / 2 + mBitmapHeight / 2, height / 2 + mBitmapHeight / 2 - mTextBounds.height() / 2);
xText = width / 2-mTextBounds.width()/2 ;
yText = mBitmapRect.bottom + mTextBounds.height();
在view的测量中,首先拿到view的宽高,然后初始化mBitmapRect给底图设置绘制区域,这里是为了绘制的底图在view的中间显示。然后xText,yText分别是字体绘制的开始位置和绘制基线位置,这里只要是让字体正中显示在底图下面。
之后是view的绘制:
@Override
protected void onDraw(Canvas canvas)
drawBitmap(canvas);
drawText(canvas);
private void drawText(Canvas canvas)
mTextPaint.setColor(0x3c3c3c);
mTextPaint.setAlpha(255 - mAlpha);
canvas.drawText(mText, xText, yText, mTextPaint);
mTextPaint.setColor(0x00FF00);
mTextPaint.setAlpha(mAlpha);
canvas.drawText(mText, xText, yText, mTextPaint);
private void drawBitmap(Canvas canvas)
//初始化画布
mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
//画底图
mBottomBitmapPaint.setAlpha(255 - mAlpha);
mCanvas.drawBitmap(mBottomBitmap, null, mBitmapRect, mBottomBitmapPaint);
//画覆盖图
mTopBitmapPaint.setAlpha(mAlpha);
mCanvas.drawBitmap(mTopBitmap, null, mBitmapRect, mTopBitmapPaint);
LogUtils.E("alpha==" + mAlpha);
//将底图和覆盖图的综合效果绘制出来
canvas.drawBitmap(mBitmap, 0, 0, null);
这里分两部分一部分是drawBitmap,另一部分是drawText。绘制bitmap方法里,首先初始化一张新的bitmap画布,然后绘制底图(带mAlpha值,范围为的0—255),之后绘制覆盖图(带mAlpha值,范围为的0—255)。最后调用canvas.drawBitmap把两张图的综合效果绘制出来。绘制问题方法里,很简单,文字绘制两遍,先绘制灰色,再绘制绿色,利用 mTextPaint.setAlpha()。显示最终文字的综合效果。
最后就是给MyView设置一个setAlpha()方法
public void setAlpha(float alpha)
int ceil = (int) Math.ceil(255 * alpha);
mAlpha = ceil;
invalidate();
ceil是向上取整,设置完Alpha值后调用invalidate()重绘。这时候MyView就全部完成了。
接下来就是主界面的布局了,微信中间部分采用viewpager+fragment的方式实现。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ethan="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/rl_title"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@mipmap/background"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:gravity="center"
android:text="微信"
android:textColor="#ffffff"
android:textSize="24sp" />
<Button
android:id="@+id/button_search"
android:layout_width="60dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:layout_marginRight="80dp"
android:background="@mipmap/search" />
<Button
android:id="@+id/button_more"
android:layout_width="60dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:layout_marginRight="10dp"
android:background="@mipmap/more" />
</RelativeLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
<ImageView
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#33000000" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal">
<com.example.administrator.weichart.activity.view.MyView
android:id="@+id/weixin"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
ethan:bottomBitmap="@mipmap/weixin"
ethan:text="微信"
ethan:textsize="16sp"
ethan:topBitmap="@mipmap/weixin_green" />
<com.example.administrator.weichart.activity.view.MyView
android:id="@+id/tongxunlu"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
ethan:bottomBitmap="@mipmap/tongxunlu"
ethan:text="通讯录"
ethan:textsize="16sp"
ethan:topBitmap="@mipmap/tongxunlu_green" />
<com.example.administrator.weichart.activity.view.MyView
android:id="@+id/faxian"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
ethan:bottomBitmap="@mipmap/faxian"
ethan:text="发现"
ethan:textsize="16sp"
ethan:topBitmap="@mipmap/faxian_green" />
<com.example.administrator.weichart.activity.view.MyView
android:id="@+id/wo"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
ethan:bottomBitmap="@mipmap/wo"
ethan:text="我"
ethan:textsize="16sp"
ethan:topBitmap="@mipmap/wo_green" />
</LinearLayout>
</LinearLayout>
主布局有了,还需要四个Fragment,以下是fragment_weixin.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is weixin fragment"
android:textColor="#0000ff"
android:textSize="18sp" />
</LinearLayout>
简单起见,Fragment里面就放置了一个TextView。
最后就是主界面HomeActivity了
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.home_layout);
initViews();
initData();
initEvent();
mViewPager.setAdapter(new myViewPagerAdapter(getSupportFragmentManager()));
mViewPager.addOnPageChangeListener(new MyPageChangeListener());
在HomeActivity的onCreate方法中,分别初始化View, 初始化Data,初始化Event,之后给mViewPager设置Adapter,并且设置页面变化监听。
private void initEvent()
weiXinMyView.setAlpha(1f);//设置第一个第一个视图的alpha为1
/**
* 初始化数据
*/
private void initData()
mFragmentList = new ArrayList<Fragment>();
mFragmentList.add(new WeiXInFragment());
mFragmentList.add(new TongXunLuFragment());
mFragmentList.add(new FaXianFragment());
mFragmentList.add(new WoFragment());
mMyViewList = new ArrayList<MyView>();
mMyViewList.add(weiXinMyView);
mMyViewList.add(tongXunLuMyView);
mMyViewList.add(faXianMyView);
mMyViewList.add(woMyView);
/**
* 初始化view
*/
private void initViews()
//初始化底部四个view
weiXinMyView = (MyView) findViewById(R.id.weixin);
tongXunLuMyView = (MyView) findViewById(R.id.tongxunlu);
faXianMyView = (MyView) findViewById(R.id.faxian);
woMyView = (MyView) findViewById(R.id.wo);
//给底部四个view设置点击事件
weiXinMyView.setOnClickListener(this);
tongXunLuMyView.setOnClickListener(this);
faXianMyView.setOnClickListener(this);
woMyView.setOnClickListener(this);
//初始化底部四个viewpager
mViewPager = (ViewPager) findViewById(R.id.viewpager);
以上为数据和view的初始化。接下是底部四个按钮的点击事件,点击当前按钮会回到这个界面。
/**
* 四个按钮的点击事件
* @param v
*/
@Override
public void onClick(View v)
switch (v.getId())
case R.id.weixin:
mViewPager.setCurrentItem(0);
break;
case R.id.tongxunlu:
mViewPager.setCurrentItem(1);
break;
case R.id.faxian:
mViewPager.setCurrentItem(2);
break;
case R.id.wo:
mViewPager.setCurrentItem(3);
break;
最后就是viewpager的adapter和viewpager 的页面滑动监听事件了。
private class MyPageChangeListener implements ViewPager.OnPageChangeListener
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
if (positionOffset > 0)
MyView leftView = mMyViewList.get(position);
leftView.setAlpha(1 - positionOffset);
LogUtils.E("positionOffset==" + (1 - positionOffset));
MyView rightView = mMyViewList.get(position + 1);
rightView.setAlpha(positionOffset);
LogUtils.E("position==" + position);
if(position>mFragmentList.size())
mViewPager.setCurrentItem(1);
@Override
public void onPageSelected(int position)
@Override
public void onPageScrollStateChanged(int state)
private class myViewPagerAdapter extends FragmentStatePagerAdapter
public myViewPagerAdapter(FragmentManager fm)
super(fm);
@Override
public Fragment getItem(int position)
return mFragmentList.get(position);
@Override
public int getCount()
return mFragmentList.size();
在监听的页面活动的过程中,首先拿到左右两个View的对象,接着根据滑动的距离来确定alpha值,导致view的重绘。
到这里整个微信底部栏就实现了。效果和原版一模一样。
源码下载:
仿微信底部实现以上是关于渐变色仪表盘的实现(带初始化动画和多次改变动画始终点)的主要内容,如果未能解决你的问题,请参考以下文章