Android 星体动效实现

的基础上,进行了部分优化,源码在http://download.csdn.net/detail/xiaomu123456/9685975,主要看StarActivity就行了
实际效果如下:


首先,要实现这样一幅动效图,基本上就是两个步骤:
  第一,在背景上确定物体(图片中的星体,流星)的初始位置
  第二,使物体进行移动,在此过程中要实时更新物体的参数信息,比如大小,亮度等

其次,对于图中的每一个实体对象,都需要以下基本信息
   1.方向2.速度3.形态4.大小5.位置6.物体宽度,高度7透明度等

具体定义如下:
public class StarInfo {

    //方向
    public final static int LEFT = 0;
    public final static int RIGHT = 1;
    public final static int TOP = 2;
    public final static int BOTTOM = 3;
    public static final int RIGHT_BOTTOM = 4;

    //速度,实际上相当于在背景上移动的距离(像素)
    public static final int LOW_SPEED = 1;
    public static final int MID_SPEED = 2;
    public static final int HIGTH_SPEED = 3;

    public Bitmap mBitmap;//形态
    public int mWidth;//物体宽度
    public int mHeight;//物体高度
    public Rect mSrcRect;//自身所占的矩形区域

    // 大小
    float sizePercent;
    // x位置
    int xLocation;
    // y位置
    int yLocation;
    // 透明度
    float alpha;
    // 漂浮方向
    int direction;
    // 漂浮速度
    int speed;
}

在图中第一种对象为星体,定义如下:

public class CircleStar extends StarInfo {

    public static final int STAR_TYPE_1 = 101;
    public static final int STAR_TYPE_2 = 102;
    public static final int STAR_TYPE_3 = 103;

    private Context mContext;

    public CircleStar(Context context,int type){
        this.mContext = context;
        initBitmap(type);
    }

    public void initBitmap( int type ){
        switch (type){
            case STAR_TYPE_1:
                mBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(R.drawable.small_start1))
                        .getBitmap();
                break;
            case STAR_TYPE_2:
                mBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(R.drawable.small_star2))
                        .getBitmap();
                break;
            case STAR_TYPE_3:
                mBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(R.drawable.small_star2))
                        .getBitmap();
                break;
            default:
                mBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(R.drawable.small_start1))
                        .getBitmap();
                break;
        }
        mWidth = mBitmap.getWidth();
        mHeight = mBitmap.getHeight();
        mSrcRect = new Rect(0,0, mWidth, mHeight);
    }

}

在图中第二种对象为流星,定义如下:

public class FallingStar extends StarInfo {

    private Context mContext;

    public FallingStar(Context context){
        this.mContext = context;
        initBitmap();
    }

    public void initBitmap(){
        mBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(R.drawable.light_star_1))
                .getBitmap();
        mWidth = mBitmap.getWidth();
        mHeight = mBitmap.getHeight();
        mSrcRect = new Rect(0,0, mWidth, mHeight);
    }
}
最后在view中展示动画效果:

public class StarView extends View {

    private final static int mStarCount = 200;//背景图添加了200个实体对象
    private List<StarInfo> mStarInfos = new ArrayList();
    private Bitmap mBackgoundBitmap;//背景图片
    private int mBackgoundWidth;
    private int mBackgoundHeight;
    private int mTotalWidth,mTotalHeight;//实际上是背景宽度和高度
    private Paint mPaint;

    public StarView(Context context) {
        this(context,null);
    }

    public StarView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public StarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initData();
        initStarInfo();
    }

    private void initData() {
        //背景
        mBackgoundBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.bg_star))
                .getBitmap();
        mBackgoundWidth = mBackgoundBitmap.getWidth();
        mBackgoundHeight = mBackgoundBitmap.getHeight();
        mTotalWidth = mBackgoundWidth;
        mTotalHeight = mBackgoundHeight;
        this.setBackgroundResource(R.drawable.bg_star);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setFilterBitmap(true);
        mPaint.setDither(true);
    }

    /**
     * 获取星球大小
     */
    private float getStarSize(float start, float end) {
        float nextFloat = (float) Math.random();
        if (start < nextFloat && nextFloat < end) {
            return nextFloat;
        } else {
            // 如果不处于想要的数据段,则再随机一次,因为不断递归有风险
            return (float) Math.random();
        }
    }

    /**
     * 初始化星球信息
     */
    private void initStarInfo() {

        StarInfo starInfo = null;
        for (int i = 0; i < mStarCount; i++) {

            if( i % 2 == 0 ){
                starInfo = new CircleStar(getContext(),CircleStar.STAR_TYPE_1);//星体1
            }else if(i % 3 == 0){
                starInfo = new CircleStar(getContext(),CircleStar.STAR_TYPE_2);//星体2
            }else{
                starInfo = new FallingStar(getContext());//流星
            }

            //共有属性
            starInfo.sizePercent = getStarSize(0.1f, 0.2f);
            getStarSpeed(starInfo);
            starInfo.alpha = getStarSize(0.3f, 0.5f);
            starInfo.xLocation = (int) (getStarSize(0f,1f) * mTotalWidth);
            starInfo.yLocation = (int) (getStarSize(0f,1f) * mTotalHeight);

            if( starInfo instanceof FallingStar ){
                starInfo.direction = StarInfo.RIGHT_BOTTOM;
            }else {
                starInfo.direction = getStarDirection();
            }
            mStarInfos.add(starInfo);
        }
    }

    /**
     *在画布上画出star
     */
    private void drawStarDynamic(StarInfo starInfo, Canvas canvas, Paint paint) {
        int xLocation = (int) ( starInfo.xLocation / starInfo.sizePercent);
        int yLocation = (int) ( starInfo.yLocation / starInfo.sizePercent);

        Bitmap bitmap = starInfo.mBitmap;
        Rect srcRect = starInfo.mSrcRect;
        Rect destRect = new Rect();
        destRect.set(xLocation, yLocation, xLocation + starInfo.mWidth, yLocation + starInfo.mHeight);

        paint.setAlpha((int) (starInfo.alpha * 255));
        canvas.save();
        canvas.scale(starInfo.sizePercent, starInfo.sizePercent);
        canvas.drawBitmap(bitmap, srcRect, destRect, paint);
        canvas.restore();

    }

    /**
     *改变star的参数信息(比如速度,透明度,大小等),在画布上移动star
     */
    private void resetStarFloat(StarInfo starInfo) {
        switch (starInfo.direction) {
            case StarInfo.LEFT:
                starInfo.xLocation -= starInfo.speed;
                if( starInfo.xLocation <= 0 ){
                    starInfo.direction = StarInfo.RIGHT;
                }
                break;
            case StarInfo.RIGHT:
                starInfo.xLocation += starInfo.speed;
                if( starInfo.xLocation >= mTotalWidth ){
                    starInfo.direction = StarInfo.LEFT;
                }
                break;
            case StarInfo.TOP:
                starInfo.yLocation -= starInfo.speed;
                if( starInfo.yLocation <= 0 ){
                    starInfo.direction = StarInfo.BOTTOM;
                }
                break;
            case StarInfo.BOTTOM:
                starInfo.yLocation += starInfo.speed;
                if( starInfo.yLocation >= mTotalHeight ){
                    starInfo.direction = StarInfo.TOP;
                }
                break;
            case StarInfo.RIGHT_BOTTOM:
                starInfo.xLocation += starInfo.speed;
                starInfo.yLocation += starInfo.speed;
                starInfo.alpha += 0.003;
                starInfo.sizePercent += 0.003;
                if( starInfo.xLocation  >= mTotalWidth ){
                    starInfo.xLocation = 0;
                    starInfo.sizePercent = 0.1f;
                    starInfo.alpha = getStarSize(0.0f,0.3f);
                }else if( starInfo.yLocation >= mTotalHeight ){
                    starInfo.yLocation = 0;
                    starInfo.sizePercent = 0.1f;
                    starInfo.alpha = getStarSize(0.0f,0.3f);
                }
                if( starInfo.alpha >= 1.0 ){
                    starInfo.alpha = 1;
                }
                if( starInfo.sizePercent >= 0.6 ){
                    starInfo.sizePercent = 0.6f;
                }

                break;
            default:
                break;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mStarInfos.size(); i++) {
            drawStarDynamic(mStarInfos.get(i), canvas, mPaint);
            resetStarFloat(mStarInfos.get(i));
        }
        postInvalidate();
    }

    /**
     * 初始化漂浮速度
     */
    private int getStarSpeed(StarInfo starInfo){
        Random random = new Random();
        int randomSpeed = random.nextInt(3);
        switch (randomSpeed) {
            case 0:
                starInfo.speed = StarInfo.LOW_SPEED;
                break;
            case 1:
                starInfo.speed = StarInfo.MID_SPEED;
                break;
            case 2:
                starInfo.speed = StarInfo.HIGTH_SPEED;
                break;
            default:
                starInfo.speed = StarInfo.MID_SPEED;
                break;
        }
        return starInfo.speed;
    }

    /**
     * 初始化星球运行方向
     */
    public int getStarDirection() {
        Random random = new Random();
        int randomInt = random.nextInt(4);
        int direction = 0;
        switch (randomInt) {
            case 0:
                direction = StarInfo.LEFT;
                break;
            case 1:
                direction = StarInfo.RIGHT;
                break;
            case 2:
                direction = StarInfo.TOP;
                break;
            case 3:
                direction = StarInfo.BOTTOM;
                break;

            default:
                break;
        }
        return direction;
    }

}


Android自定义View 星球运动在dribbble闲逛的时候发现的一个有意思的星球运动的动画,刚好最近时间尚可,就简单实现了一下中间运动的部分,又是因为时间的原因,开头位移的部分没有完成. 这是在dribbble中发现的动画 这是我自己实现的效果... 总觉得我这个星球有点胖... 因为胖所以转的慢么这是.速度等细节还有优化的余地设计过程老办法,先分解动画的构成.整个动画可以看做是一个自旋的星球从右上角由小变大的移动到屏幕的中央的.星球的位移及缩放不说(其实是最近有需求,暂时没时间完善),主要完善了星球的旋转及尾部的处理.最底层是背景的星星闪烁,每次在星球一定范围内随机出现,并缩放就好最开始设计尾部效果的时候,是在没列中设计了两端线.再不断的运行及移动.但是实现起来很乱.最后采用了先绘制所有尾部展示的内容,然后在用和背景一样的颜色部分遮盖并移动此部分形成视觉上的效果的方法.(也可以设置PorterDuff模式来展示).设计过程中的效果如下星球的设计,星球的本身使用简单的遮盖和贝塞尔曲线就能完成一个较为满意的星球背景.重点是星球地表的设计以及星球自转下的地表样式的移动.解决的方法是是先绘制三个重复并连续的地表样式,通过移动整个地表样式模拟星球的转动.最后通过PorterDuff来控制展示的部分和星球的位置重合.未开启PorterDuff模式时绘制的样式如下:开启PorterDuff模式后再指定位置展示指定形状的图形如下:最后再移动设置好的星球地貌就可以模拟出星球转动的效果了代码实现背景的星星private fun drawStarts(canvas: Canvas, perIndexInAll: Float) {     //背景的星星在星球附近的一定范围内随机出现     val maxRand = 800     canvas.translate(-maxRand / 2F , -maxRand / 2F)     val Random = Random(perIndexInAll.toInt().toLong())     //绘制背景的星星     for (index in 0..4){         drawStart(canvas ,  Random.nextFloat() * maxRand , Random.nextFloat() * maxRand , perIndex)     }     canvas.translate(maxRand / 2F , maxRand / 2F) } //绘制背景的星星内容 //绘制背景的星星内容 private fun drawStart(canvas: Canvas, x: Float, y: Float, per: Float) {     var per = per     //这个部分是为了让星星实现从小到大后再从大到小的变动     if (per >= 1.0F){         per -= 1F     }     if (per <= 0.5F){         per *= 2     }else{         per = (1 - per) * 2     }     canvas.save()     canvas.translate(x , y)     canvas.scale(per , per)     val paint = Paint()     paint.color = 0xff78D8DF.toInt()     val startLength = 30F     val startOffset = startLength / 3F     //通过路径描绘星星的形状     val path = Path()     path.moveTo(0F , startLength)     path.lineTo(startOffset , startOffset )     path.lineTo(startLength , 0F)     path.lineTo(startOffset  , -startOffset )     path.lineTo(0F , -startLength)     path.lineTo(-startOffset  , -startOffset )     path.lineTo(-startLength , 0F)     path.lineTo(-startOffset  , startOffset )     path.lineTo(0F , startLength)     canvas.drawPath(path , paint)     paint.color = viewBackgroundColor     //通过缩小绘制星星内部形状     canvas.scale(0.3F , 0.3F)     canvas.drawPath(path , paint)     canvas.restore() }星球外部private fun drawGas(canvas: Canvas, index: Float) {     canvas.save()     canvas.rotate(45F)     val gasWidth = 18F     val baseR = baseR * 0.7F     val absBaseR = baseR / 5F     val paint = Paint()     paint.strokeWidth = gasWidth     paint.style = Paint.Style.STROKE     paint.color = 0xff2F3768.toInt()     val paintArc = Paint()     paintArc.color = 0xff2F3768.toInt()     val gasLength = baseR * 2F     canvas.save()     val gsaL = gasWidth / 2F * 3     var maxGasLength = (gasLength   gsaL ) / 2     var index = index     canvas.scale(1F , -1F)     //绘制星球后面的气流情况     //舍不得那么多定义好的变量     //又不想写个参数很多的函数,就这么实现了     canvas.save()     canvas.translate(baseR , baseR * 1.2F)     canvas.translate(0F , absBaseR)     //drawLines函数一个绘制两头带半圆的线段     drawLines(0F, maxGasLength, canvas, paint)     drawWhite( maxGasLength * index, gasWidth , gsaL * 2 , canvas)     drawWhite( maxGasLength * (index - 1 ) * 1.1F, gasWidth , gsaL * 2 , canvas)     drawWhite( maxGasLength * (index   1 ) * 1.1F, gasWidth , gsaL * 2 , canvas)     canvas.restore()     index = index   0.3F     //.....没有写函数就不上重复的代码了     val rectf = RectF(-baseR , -baseR , baseR ,baseR)     canvas.drawArc(rectf , 0F , 180F , false , paint)     canvas.drawLine(baseR ,0F ,  baseR ,  -baseR, paint)     canvas.drawLine(-baseR ,0F ,  -baseR ,  -baseR, paint)     canvas.restore() } //绘制尾部空白部分 private fun drawWhite(offset: Float, gasWidth: Float, gsaL : Float , canvas: Canvas) {     val r = gasWidth / 2F     canvas.save()     canvas.translate( 0F , offset - 2 * gsaL )     val pointPaint = Paint()     pointPaint.strokeWidth = 20F     pointPaint.color = Color.RED     //通过贝塞尔曲线绘制半圆效果     val path = Path()     path.moveTo(-r , gsaL)     path.cubicTo(             - r * C ,  gsaL - r,             r * C ,  gsaL - r,             r , gsaL     )     path.lineTo(r , - gsaL)     path.cubicTo(             r * C ,  - gsaL   r,             -r * C ,  - gsaL   r,             -r , - gsaL     )     path.lineTo(-r , gsaL * 1.5F)     val paint = Paint()     paint.color = viewBackgroundColor     canvas.drawPath(path , paint)     canvas.restore() }星球private fun drawPlanet(canvas: Canvas , index : Float) {     //设置原图层     val srcB = makeSrc(index)     //设置遮罩层     //遮罩层只有一和星球大小一样的圆     val dstB = makeDst(index)     val paint = Paint()     canvas.saveLayer(-baseR, -baseR, baseR , baseR, null, Canvas.ALL_SAVE_FLAG)     //绘制遮罩层     canvas.drawBitmap(dstB,  -baseR / 2F, -baseR / 2F , paint)     //设置遮罩模式为SRC_IN显示原图层中原图层与遮罩层相交部分     paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)     canvas.drawBitmap(srcB, width / -2F, height / -2F , paint)     paint.xfermode = null } //设置源图层 fun makeSrc(index :Float): Bitmap {     val bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)     val canvas = Canvas(bm)     canvas.translate(width.toFloat() / 2F , height.toFloat() / 2F)     val paint = Paint()     paint.color = 0xff57BEC6.toInt()     paint.style = Paint.Style.FILL     val rectf = RectF(-baseR / 2F, -baseR / 2F, baseR / 2F, baseR / 2F)     canvas.drawArc(rectf , 0F , 360F , true , paint)     canvas.save()     //绘制星球背景     paint.color = 0xff78D7DE.toInt()     var baseR = baseR * 0.9.toFloat()     val rectf2 = RectF(-baseR / 2F, -baseR / 2F, baseR / 2F, baseR / 2F)     canvas.translate(baseR / 6F , baseR / 6F)     canvas.drawArc(rectf2 , 0F , 360F , true , paint)     canvas.restore()     canvas.rotate(-45F)     canvas.save()     val bottomBaseR = baseR / 0.9F / 2     val path = Path()     path.moveTo(-bottomBaseR , 0F)     path.cubicTo(-bottomBaseR , bottomBaseR * 2, bottomBaseR  , bottomBaseR * 2, bottomBaseR , 0F)     path.cubicTo(             bottomBaseR * C,bottomBaseR ,             -bottomBaseR * C,bottomBaseR ,             -bottomBaseR , 0F     )     //绘制星球背景的阴影效果     paint.color = 0xffAAEEF2.toInt()     paint.style = Paint.Style.FILL     canvas.drawPath(path , paint)     //绘制星球的地貌     drawPoints(index , canvas)     canvas.restore()     paint.strokeWidth = 30F     paint.color = 0xff2F3768.toInt()     paint.style = Paint.Style.STROKE     canvas.drawArc(rectf , 0F , 360F , true , paint)     return bm } private fun drawPoints(index: Float, canvas: Canvas) {         val paintB = Paint()         val paintS = Paint()         paintS.style = Paint.Style.FILL         paintS.color = 0xffE7F2FB.toInt()         paintB.style = Paint.Style.FILL         paintB.color = 0xff2F3768.toInt()         val baseRB = baseR / 2F / 3         val baseRS = baseR / 2F / 3 / 3         val rectfB = RectF(-baseRB, -baseRB, baseRB, baseRB)         val rectfS = RectF(-baseRS, -baseRS, baseRS, baseRS)         val pointPaint = Paint()         pointPaint.color = Color.BLACK         pointPaint.strokeWidth = 50F         val coverWidth = baseR         //通过移动坐标原点模拟星球的自转效果         canvas.translate(-coverWidth / 2F , coverWidth * 1.5F)         val index = index         canvas.translate(0F , coverWidth * index )         //重复绘制三次星球的地貌使得星球的自转无缝连接         for (i in 0..2){             canvas.save()             canvas.translate(coverWidth / 3F / 2  , -coverWidth / 3F * 2)             canvas.drawArc(rectfB , 0F , 360F , true , paintB)             canvas.drawArc(rectfS , 0F , 360F , true , paintS)             canvas.restore()             canvas.save()             canvas.translate(coverWidth / 3F *2 , -coverWidth / 3F)             canvas.drawArc(rectfB , 0F , 360F , true , paintB)             canvas.drawArc(rectfS , 0F , 360F , true , paintS)             canvas.restore()             canvas.save()             canvas.translate(coverWidth / 3F *2 , -coverWidth / 8F * 7   -coverWidth / 10F )             canvas.drawArc(rectfS , 0F , 360F , true , paintB)             canvas.restore()             canvas.save()             canvas.translate(coverWidth / 3F *2 , -coverWidth / 8F * 7  - -coverWidth / 10F )             canvas.drawArc(rectfS , 0F , 360F , true , paintB)             canvas.restore()             canvas.translate(0F , -coverWidth)         }     }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值