Android新手上路--PathMeasure类的使用,长按持续产生爱心

长按持续产生爱心

这是效果图,长按监听的逻辑被封装在自定义SurfaceView的内部。在模拟器上显得稍微有点卡。

如有错误,还请指正

知识点

  • PathMeasure类的简单使用
  • Path类的贝塞尔曲线实现

以下代码:

自定义SurfaceView:HeartShapeLikeEffectSfcView

package com.example.doge_heartshapelikeeffect.view;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;;

/**
 * 
 * @author fhbianling--- A little learning is a dangerous thing.
 * @version 创建时间:2016年6月29日 下午7:25:54
 * @mail fhbianling@163.com
 * 
 */
public class HeartShapeLikeEffectSfcView extends SurfaceView implements Runnable, Callback, OnTouchListener {
    private SurfaceHolder mHolder;
    private Paint mPaint;
    private List<HeartShape> mHeartShapes;
    private int viewWidth;
    private int viewHeight;

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mHolder = this.getHolder();
        mHolder.addCallback(this);
        mHeartShapes = new ArrayList<HeartShape>();

        // 使SurfaceView的背景色为透明的步骤1
        setZOrderOnTop(true);
        mHolder.setFormat(PixelFormat.TRANSLUCENT);
        // 若不做上述两步处理,并且canvas.drawColor(Color.TRANSPARENT),
        // 则SurfaceView的背景色为黑色,不能达到背景色为父容器背景色的效果
        // 做处理后,若在XML中设置该View的背景色也会正常显示,不设置则默认为父容器背景色
    }

    public HeartShapeLikeEffectSfcView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public HeartShapeLikeEffectSfcView(Context context) {
        super(context);
        init();
    }

    public HeartShapeLikeEffectSfcView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private boolean isDestroy;

    /**
     * 当Surface被创建时既开启一个线程,这个线程在mHeartShapes的长度为0时等待,长度大于0时被唤醒
     * 并在Surface被销毁时停止
     */
    @Override
    public void run() {
        while (true && !isDestroy) {
            // 下面这个代码块负责绘制
            synchronized (mHolder) {
                Canvas lockCanvas = mHolder.lockCanvas();
                drawHeart(lockCanvas);
                mHolder.unlockCanvasAndPost(lockCanvas);
            }
            // 下面这个代码块负责该线程的等待操作的判定
            synchronized (this) {
                if (mHeartShapes.size() == 0) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 遍历绘制集合中移动的心形
     * @param canvas
     */
    private void drawHeart(Canvas canvas) {
        // 使SurfaceView的背景色为透明的步骤2
        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

        if (mHeartShapes.size() == 0) {
            return;
        }
        // 如果集合mHeartShapes中的某个形状已经移动完毕,则将其移除集合
        // 否则调用这个形状的move方法,这个move方法封装了具体的绘制逻辑
        for (int i = 0; i < mHeartShapes.size(); i++) {
            HeartShape heartShape = mHeartShapes.get(i);
            if (heartShape.isFinsh) {
                mHeartShapes.remove(heartShape);
                continue;
            } else {
                heartShape.move(canvas);
            }
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // 初始化
        isDestroy = false;
        viewWidth = getWidth();
        viewHeight = getHeight();
        //开启绘制线程
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制线程
        isDestroy = true;
    }
    /**
     * 为集合添加指定数量的心形
     * @param num
     */
    public void addHeartShape(int num) {
        if (mHeartShapes == null || viewWidth == 0 || mHeartShapes.size() > 20 || num == 0) {
            return;
        }
        for (int i = 0; i < num; i++) {
            mHeartShapes.add(new HeartShape(viewWidth, viewHeight, mPaint));
        }
        // 当mHeartShapes的长度不为0时唤醒绘制线程
        synchronized (this) {
            this.notify();
        }
    }

    /**
     * 为自定义View绑定长按事件的控制器,长按事件的所有逻辑都封装在该类内部
     * 
     * @param controller
     */
    public void bindLongClickViewController(View controller) {
        controller.setOnTouchListener(this);
    }

    private boolean isAdding;

    private AddThread thread;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            isAdding = true;
            //ACTION_DOWN时开启一个添加线程
            if(thread==null||!thread.isAlive()){
            thread = new AddThread();
            thread.start();
            }
            break;
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_UP:
            isAdding = false;
            break;
        }
        return false;
    }

    /**
     * 该线程在长按发生时每隔一定时间为心型集合添加一个心形
     * 
     * @author Administrator
     *
     */
    private class AddThread extends Thread {
        @Override
        public void run() {
            long orgTime = System.currentTimeMillis();
            while (isAdding) {
                // if内的控制语句用于判断长按事件
                if (System.currentTimeMillis() - orgTime > 1000) {
                    addHeartShape(1);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

其中使用到的类HeartShape

package com.example.doge_heartshapelikeeffect.view;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;

/**
 * @author fhbianling--- A little learning is a dangerous thing.
 * @version 创建时间:2016年7月5日 上午10:10:49
 * @mail fhbianling@163.com
 * 
 */
public class HeartShape {
    private int color;
    private int red, green, blue;
    private float sidesLength;
    private Path mPath;
    private PathMeasure mMeasure;
    private float length;
    private float distance;
    private float[] pos = new float[2];
    public boolean isFinsh;
    private Paint mPaint;

    /**
     * 在该构造中为心形的随机大小,随机路径及其长度,随机颜色进行初始化
     * 
     * @param viewWidth
     * @param viewHeight
     * @param paint
     */
    public HeartShape(int viewWidth, int viewHeight, Paint paint) {
        mPaint = paint;
        // 基础长度取自自定义View宽高最小值/10
        float baseLength = Math.min(viewWidth, viewHeight) / 10;
        // sidesLength用于控制心形大小
        sidesLength = (float) (Math.random() * 0.6 * baseLength + baseLength * 0.7);

        // 为颜色的三原色赋值
        red = (int) getRandomValue(256);
        green = (int) getRandomValue(256);
        blue = (int) getRandomValue(256);

        //生成心形的随机移动路径
        mPath = new Path();
        //moveTo控制路径起点,为View的下边界中点处
        mPath.moveTo(viewWidth / 2, viewHeight);
        float controlX = getRandomValue(viewWidth);
        float controlY = getRandomValue(viewHeight);
        float endX = getRandomValue(viewWidth);
        //endY的赋值是为了使心形的移动路径在Y轴上始终是向View上方的
        float endY = (float) (controlY * Math.abs((Math.random() - 0.3)));
        //quadTo方法和cubicTo方法都可以生成贝塞尔曲线,唯一的区别是cubicTo多一个控制点
        //quadTo方法中前两个参数是控制点的x,y坐标,后两个点是贝塞尔曲线的结束点坐标
        mPath.quadTo(controlX, controlY, endX, endY);
        // 这个构造中boolean forceClosed用于设定是否闭合Path
        mMeasure = new PathMeasure(mPath, false);
    }

    /**
     * 生成一个0~orgValue范围内的浮点数,不能取到orgValue,但可以无限接近
     * @param orgValue
     * @return
     */
    private float getRandomValue(float orgValue) {
        return (float) (Math.random() * orgValue);
    }

    /**
     * 传入中心坐标绘制对应位置的心形
     * @param canvas
     * @param centerX
     * @param centerY
     * @param color
     */ 
    private void mDrawHeart(Canvas canvas, float centerX, float centerY, int color) {
        if (canvas == null) {
            return;
        }
        //x0,y0是经坐标转换后的坐标原点
        float x0 = centerX - sidesLength / 2;
        float y0 = centerY - sidesLength / 2;
        mPaint.setColor(color);
        //用一条贝塞尔曲线制作心形的右半边Path,其各参数可随意调整获得不同的心形
        //没有采用对心形线方程拟合的方法,而是直接通过两个连续光滑曲线闭合形成心形
        Path path = new Path();
        path.moveTo(x0 + sidesLength / 2, y0 + sidesLength / 3);
        path.cubicTo(x0 + sidesLength * 6 / 7, y0 - sidesLength / 20, x0 + sidesLength * 1.3f, y0 + sidesLength * 3 / 5,
                x0 + sidesLength / 2, y0 + sidesLength * 19 / 20);
        //另一条制作左半边Path
        Path path2 = new Path();
        path2.moveTo(x0 + sidesLength / 2, y0 + sidesLength / 3);
        path2.cubicTo(x0 + sidesLength / 7, y0 - sidesLength / 20, x0 - sidesLength * 0.3f, y0 + sidesLength * 3 / 5,
                x0 + sidesLength / 2, y0 + sidesLength * 19 / 20);
        //两个Path合并并闭合,得到心形的完整path
        path.addPath(path2);
        path.close();
        canvas.drawPath(path, mPaint);
    }

    private int alpha;
    /**
     * 当调用move方法时,封装了该心形的移动和绘制
     * @param canvas
     */
    public void move(Canvas canvas) {
        if (length == 0) {
            //PathMeasure可以测量Path类对应路径的长度,并获得Path上各点的坐标
            length = mMeasure.getLength();
        }
        //使每次调用move方法时,心形移动移动路径的1/100长度
        distance += length / 100;
        //通过Color.argb(...)方法设置带透明度的颜色
        alpha = (int) ((length - distance) * 255 / length);
        if (distance >= length && length != 0) {
            //当distance>length时移动完毕,这时的alpha会变成负数
            isFinsh = true;
            return;
        }
        color = Color.argb(alpha, red, green, blue);
        //PathMeasure的.getPosTan(...)方法会对pos(一个长度为2的float数组)赋值,
        //对应Path上从起点开始沿路径移动distance距离的点的坐标
        //pos[0]:x坐标,pos[1]:y坐标
        //第三个参数用于获取对该点在路径上的切线的正切值,当沿路径移动的图像还需要旋转时需要这个参数
        //这里并不需要,直接填null
        mMeasure.getPosTan(distance, pos, null);
        mDrawHeart(canvas, pos[0], pos[1],color);
    }

}

然后是XML和Activity,很简单,就多写了一个Button

Activity:

package com.example.doge_heartshapelikeeffect.aty;

import com.example.doge_heartshapelikeeffect.R;
import com.example.doge_heartshapelikeeffect.view.HeartShapeLikeEffectSfcView;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
    private HeartShapeLikeEffectSfcView view;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        view=(HeartShapeLikeEffectSfcView) findViewById(R.id.view);
//将Button绑定为自定义View的长按控制器       
view.bindLongClickViewController(findViewById(R.id.test));
    }

}

XML

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <com.example.doge_heartshapelikeeffect.view.HeartShapeLikeEffectSfcView
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="400dp"/>
    <Button android:id="@+id/test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="test"/>
</LinearLayout>

参考

http://blog.csdn.net/u012296503/article/details/51510206

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 `<layer-list>` 中,可以使用 `<item>` 标签的 `android:width` 和 `android:height` 属性来设置子项的大小。但是,这些属性只支持具体的像素值或者 `wrap_content`、`match_parent` 等常见的布局属性,不支持百分比值。 如果需要在 `<layer-list>` 中使用百分比大小,可以考虑使用 `<bitmap>` 标签来指定图片资源,并通过 `android:gravity` 属性来控制图片的位置和大小。例如,下面的示例代码中,使用了一个占据整个 `<layer-list>` 的图片资源,并通过 `android:gravity="center"` 属性将图片居中显示: ``` <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <bitmap android:src="@drawable/my_image" android:gravity="center" /> </item> </layer-list> ``` 如果需要让图片的大小为 `<layer-list>` 的一半,可以将 `android:gravity` 属性设置为 `center`,并在 `<bitmap>` 标签中使用 `android:scaleX` 和 `android:scaleY` 属性来控制图片的缩放比例。例如,下面的示例代码中,将图片缩放为原来的一半大小,并将其居中显示: ``` <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <bitmap android:src="@drawable/my_image" android:scaleX="0.5" android:scaleY="0.5" android:gravity="center" /> </item> </layer-list> ``` 需要注意的是,使用 `<bitmap>` 标签来指定图片资源时,需要确保图片资源的大小与 `<layer-list>` 的大小相同,否则可能会导致图片的显示效果不符合预期。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值