今天来写一个淘宝的一个小动画,一看觉得挺简单的,但是实际操作起来,感觉有点麻烦,遇到的问题也比较多,不过好在模仿出来了,好了给大家看看效果。
这是老版本的,模拟器上面的和现版本的不一致
这个是新版本的,下面的布局Bi老版本要稍微复杂一点。
接下来看看主界面的布局结构,最外面的是一个ViewGroup,然后就是一个“+”的View,再就是文本。
下面我们就来完成这个View的书写,先定义一个ViewGroup,在定义一个View画上“+”,然后ViewGroup里面加上该View和文本内容。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //画矩形的背景 canvas.drawRect(0,getMeasuredWidth()/2,getMeasuredWidth(),getMeasuredHeight(),mPaint); RectF rectF = new RectF(0,0,getMeasuredWidth(),getMeasuredHeight()/2); //画圆 canvas.drawCircle(getMeasuredWidth()/2,getMeasuredWidth()/2,getMeasuredWidth()/2,mPaint); }//测量的话,因为我们这是特定的布局,所以就没考虑到硬编码的问题,因为该容器不具备通用性 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //测量子View,这里巨鼎父容器大小的是底部的TextView,所以就一该View为基准,设置父布局,其中TextView的上下左右的Margin都是可以控制的 measureChildren(widthMeasureSpec,heightMeasureSpec); int w = getChildAt(0).getMeasuredWidth(); int h = getChildAt(0).getMeasuredHeight(); imgRadius = w/2; myLitterXView.init(); int width = w + marginLeftOrRightText * 2; int height = h + marginBottomText + marginTopText + imgRadius + width/2; //最后将理想的宽高赋值上去。 setMeasuredDimension(width,height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //layout就是确定子View的位置的,这个就是个算数问题,没啥好说的 View view = getChildAt(0); view.layout( marginLeftOrRightText, getMeasuredHeight()-view.getMeasuredHeight()-marginBottomText, marginLeftOrRightText+view.getMeasuredWidth(), getMeasuredHeight()-marginBottomText ); View v = getChildAt(1); v.layout( marginLeftOrRightText,imgMarginTop,getMeasuredWidth()-marginLeftOrRightText,imgMarginTop+imgRadius*2 ); }
由于ViewGrou只是负责装载子View,并不承担绘制工作,但是我们这里需要其承担一定的绘制工作,那么就必须 setWillNotDraw(false);,让其调用draw()方法.
private void init() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(color); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); //强制让容器执行绘制工作 setWillNotDraw(false); textView = new TextView(getContext()); textView.setText("发布"); textView.setTextColor(Color.WHITE); LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); textView.setLayoutParams(lp); addView(textView); myLitterXView = new MyLitterXView(getContext()); myLitterXView.setLayoutParams(lp); addView(myLitterXView); setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mClickListener!=null)mClickListener.click(v); myLitterXView.change(myLitterXView.model==TO_SPECIAL?TO_NOMAL:TO_SPECIAL); } }); }
以上还只是完成了最基本的步骤,“+”View的定义
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(0x00000000);
//画两条线,比较简单 canvas.drawLine(hLine[0].getX(),hLine[0].getY(),hLine[1].getX(),hLine[1].getY(),paint);
canvas.drawLine(vLine[0].getX(),vLine[0].getY(),vLine[1].getX(),vLine[1].getY(),paint);
canvas.restore();
}说一下旋转动画的这几个参数,旋转的起始角度,最终角度,x轴的参照位置,参照点,y轴的参照位置,y轴参照点
RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,
int pivotYType, float pivotYValue)animation2 = new RotateAnimation(45,0,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
通过普通的补间动画完成该旋转功能即可。如此下来,就差不多一级界面差不多了,接下来分析点击动画的组成部分。
点击一下,会有一个全屏的Window,这是必须的,别的方法也可以但是,没有该效果好,在window上面操作动画,这里要特别注意,当我们点击了一级界面的按钮后,Window添加布局,然而该View何时测量完毕?什么时候去开启第一次的动画?这都值得我们思考
如果对Window不太了解的可以看看我的第一篇博客WindowManger的应用
public class MyWindow { private WindowManager mWindowManager; private WindowManager.LayoutParams mLp; private Activity context; private View view; //WindowManger需要基本的一些配置 public MyWindow(Activity context) { this.context = context; mWindowManager = context.getWindowManager(); mLp = new WindowManager.LayoutParams(); mLp.height = WindowManager.LayoutParams.MATCH_PARENT; //这是表示Window的左上角的位置 mLp.x = 0; mLp.y = 0; mLp.width = WindowManager.LayoutParams.MATCH_PARENT; mLp.flags = WindowManager.LayoutParams.FLAG_DITHER; mLp.format = PixelFormat.TRANSLUCENT; } public void addView(View view){ this.view = view; mWindowManager.addView(view,mLp); } public void removeView(){ if (mWindowManager==null)return; mWindowManager.removeView(view); context = null; } }
Window有了,下面我们要定义动画View了。在此之前我们要将动画View设置在一级菜单的上方,那么我们就要得到一级菜单的高度或者是在屏幕当中的位置(可以使用getLocationOnWindow,参数是个长度为2的整型数组,分别为x,y的坐标)。
圆形的TextView通过canvas.drawCircle(radius, radius, radius, mPaint);
这里直接在调用父方法前绘制背景图层。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myMenuView = (MyMenuView) findViewById(R.id.menu_view); final List<String> list = new ArrayList<>(); list.add("发布"); list.add("报价"); contentView = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.animator_view, null); myAnimatorView = (com.yzz.android.animator.MyAnimatorView) contentView.getChildAt(0); myAnimatorView.init(Color.rgb(12, 22, 44), Color.WHITE, list); myWindow = new MyWindow(this); myAnimatorView.setWindow(myWindow); myAnimatorView.setRadius(50); int count = myAnimatorView.getChildCount(); for (int i = 0; i < count; i++) { View v = myAnimatorView.getChildAt(i); final int finalI = i; v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this,list.get(finalI),Toast.LENGTH_LONG).show(); } }); } myMenuView.setClickListener(new MyMenuView.ClickListener() { @Override public void click(View view) { //这里我是在点击事件里面设置给Window上的View的init数据的,这里只设置一次 if (isFirst) { isFirst = false; ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) myAnimatorView.getLayoutParams();//高度的话我们这里要设置到,必须在一级菜单的上方。 lp.height = myMenuView.getMeasuredHeight() + myMenuView.getWidth() / 2 + MyAnimatorView.MARGIN; myAnimatorView.setLayoutParams(lp); myAnimatorView.setRadius(myMenuView.getWidth() / 2);//设置上去菜单的高度,后面平移动画需要 myAnimatorView.setHeight(myMenuView.getHeight()); myWindow.addView(contentView); } else { myWindow.addView(contentView); //myAnimatorView.doOpen();这里不可以开始动画,还没有加载完毕 } } }); contentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { myMenuView.change(MyMenuView.TO_NOMAL); myAnimatorView.docloes(); } });
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //这里会layout两次,第一次是没有设置高度时的layout,第二次是动态设置height是触发的layout if (radius <= 0) return; int count = getChildCount(); int h = getMeasuredHeight() - menuHeight; for (int i = 0; i < count; i++) { View child = getChildAt(i); child.layout(instanceX / 2, h, instanceX / 2 + child.getMeasuredWidth(), h + child.getMeasuredHeight()); child.setScaleX(0.5f); child.setScaleY(0.5f); child.setAlpha(0f); } //这里当laout完毕的时候要进行第一次的动画 doOpen(); }1234567891011121314151612345678910111213141516
这里使用的属性动画,属性动画的有点就是在动画的过程中改变了View的位置属性,这样点击区域就会随之View的移动而改变。通过PropertyValuesHolder来操作一组动画,很方便,当然也可以时候AnimatorSet来实现,都可以。注意:动画的位置规则是相对的,相对于当前状态,平移的话,x>0,y>0表示这x右移,y下移。
public void initAnimation() { instanceX = childList.get(0).getMeasuredWidth() + MARGIN; instanceY = getMeasuredHeight() - menuHeight; int translateX = instanceX / 2; holder0 = PropertyValuesHolder.ofFloat("translationX", -translateX, 0); holder1 = PropertyValuesHolder.ofFloat("translationY", -instanceY, 0); holder2 = PropertyValuesHolder.ofFloat("scaleX", 1, 0.5f); holder3 = PropertyValuesHolder.ofFloat("scaleY", 1, 0.5f); holder4 = PropertyValuesHolder.ofFloat("alpha", 1, 0f); closeLeft = ObjectAnimator.ofPropertyValuesHolder(childList.get(0), holder0, holder1, holder2, holder3, holder4); closeLeft.addListener(this); closeLeft.setDuration(2000); holder00 = PropertyValuesHolder.ofFloat("translationX", translateX, 0); closeRight = ObjectAnimator.ofPropertyValuesHolder(childList.get(1), holder00, holder1, holder2, holder3, holder4); closeRight.setDuration(2000); //还原 holderN0 = PropertyValuesHolder.ofFloat("translationX", 0, -translateX); holderN1 = PropertyValuesHolder.ofFloat("translationY", 0, -instanceY); holderN2 = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 1f); holderN3 = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 1f); holderN4 = PropertyValuesHolder.ofFloat("alpha", 0f, 1f); openLeft = ObjectAnimator.ofPropertyValuesHolder(childList.get(0), holderN0, holderN1, holderN2, holderN3, holderN4); openLeft.addListener(this); openLeft.setDuration(2000); PropertyValuesHolder holderN00 = PropertyValuesHolder.ofFloat("translationX", 0, translateX); openRight = ObjectAnimator.ofPropertyValuesHolder(childList.get(1), holderN00, holderN1, holderN2, holderN3, holderN4); openRight.setDuration(2000); } public void docloes() { //当当前View正在动画的时候,是不允许再次动画的 if (isAnimationing)return; closeLeft.start(); closeRight.start(); model = CLOSE; } public void doOpen() { if (isAnimationing)return; openLeft.start(); openRight.start(); model = OPEN; }
好了,关键代码就是这些,要注意,何时init,这个时机很重要,当init有addView操作的,那么就必须放在onFinishLayoutInflate()方法中,原因是当从xm中加载完毕后,我们的ViewGroup才被创建,所以要在该方法中取添加或者操作子View,还有一点,就是在第一次动画的时候,要找准动画的时机,不然有一些数据还未初始化,就要开始动画了,这样会出现各种错误。好了,基本就是这么多,有兴趣的可以去下载源码看看GitHub地址https://github.com/yzzAndroid/Animator
共同学习,写下你的评论
评论加载中...
作者其他优质文章