高仿QQ发送语音界面

首先先看效果图吧





为了看的更清楚,所以把圈的颜色改成绿色了


下面说一下思路,左右二边是一个ImageView,自己重写了它,用画笔在上面画一个圈,然后通过滑动的距离增加或减少圆的半径在重绘就行了

下面上ImageView代码:


public class AuditionButton extends ImageView {

    /**
     * 中心点的X坐标
     */
    private int mCenterX;
    /**
     * 中心点的Y坐标
     */
    private int mCenterY;
    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 半径
     */
    private double mRadius;

    /**
     * 圆和图片的间距
     */
    private float padding = 20;

    /**
     * 扩大值
     */
    private float expandValue = 0f;

    /**
     * 缩放系数
     */
    private double zoom = 0.6f;

    //宽度
    private int width;

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

    public AuditionButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.GREEN);
        mPaint.setStrokeWidth(2);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        post(new Runnable() {
            @Override
            public void run() {
                int dw = getDrawable().getBounds().width();
                int dh = getDrawable().getBounds().height();
                //图片宽高应该相等,这里随便用什么
                mRadius = (dw / 2) + padding;
                width = getWidth();
            }
        });
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;
        canvas.drawCircle(mCenterX, mCenterY, (float) mRadius + expandValue, mPaint);
        super.onDraw(canvas);
    }

    /**
     * 设置当前选中状态
     *
     * @param isSelected
     */
    public void setSelected(boolean isSelected) {
        if (isSelected) {
            mPaint.setStyle(Paint.Style.FILL);
        } else {
            mPaint.setStyle(Paint.Style.STROKE);
        }
        invalidate();
    }

    public void setDistance(double distance) {
        if (distance > 1.0f) {
            expandValue = 0;
            invalidate();
            return;
        }
        double ratio = (1 - distance);
        if (ratio > zoom) {
            ratio = zoom;
        }
        float temp = (float) (width / 2 - mRadius);
        expandValue = (float) (temp * ratio);
        invalidate();
    }

}


然后上面抖动效果,上代码


public class FluctuateView extends View {
    /**
     * 刷新频率
     */
    private static final int LONGTIME = 500;
    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 线数量
     */
    private int mLineCount = 8;
    /**
     * 颜色
     */
    private int mLineColor;
    /**
     * 线的宽度
     */
    private int mLineWidth;
    /**
     * 间距
     */
    private int mLineSpacing;
    /**
     * 是否停止标示
     */
    private boolean isStop = true;

    //低
    private static final int LOW = 0;
    //一般
    private static final int GENERAL = 1;
    //中等
    private static final int MEDIUM = 2;
    //高
    private static final int HIGH = 3;

    /**
     * 抖动的频率
     */
    private int mShake = LOW;

    public void setmShake(int mShake) {
        this.mShake = mShake;
    }

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

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

    public FluctuateView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FluctuateView, defStyleAttr, 0);
        int count = array.getIndexCount();
        for (int i = 0; i < count; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.FluctuateView_line_color:
                    mLineColor = array.getColor(attr, Color.GREEN);//默认绿色
                    break;
                case R.styleable.FluctuateView_line_count:
                    mLineCount = array.getInteger(attr, 0);
                    break;
                case R.styleable.FluctuateView_line_spacing:
                    mLineSpacing = array.getDimensionPixelSize(attr, 0);
                    break;
                case R.styleable.FluctuateView_line_width:
                    mLineWidth = array.getDimensionPixelSize(attr, 0);
            }
        }
        array.recycle();
        mPaint = new Paint();
        mPaint.setColor(mLineColor);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mLineCount > 0) {
            for (int i = 0; i < mLineCount; i++) {
                float left = (i * mLineSpacing) + (i * mLineWidth);
                int random = new Random().nextInt(getHeight());
                float top = random;
                float right = left + mLineWidth;
                float bottom = getHeight();
                canvas.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        //不允许直接写死宽度
        if (widthMode == MeasureSpec.EXACTLY) {
            width = (mLineCount * mLineWidth) + ((mLineCount - 1) * mLineSpacing);
        } else {
            width = (mLineCount * mLineWidth) + ((mLineCount - 1) * mLineSpacing);
        }
        setMeasuredDimension(width, heightSize);
    }

    /**
     * 开始
     */
    public void start() {
        isStop = true;
        new Thread() {
            @Override
            public void run() {
                super.run();
                while (isStop) {
                    try {
                        sleep(LONGTIME);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    postInvalidate();
                }
            }
        }.start();
    }

    /**
     * 停止
     */
    public void stop() {
        isStop = false;
    }


}


中间处理滑动的类

public class AudioRecordLayout extends RelativeLayout {

    private static final int UPDATETIME = 0;

    /**
     * 录制结果接口
     */
    private onRecordStatusListener mOnRecordStatusListener;
    /**
     * 松开试听按钮
     */
    private AuditionButton mAuditionBtn;
    /**
     * 删除语音按钮
     */
    private AuditionButton mDeleteVoiceBtn;
    /**
     * 录制状态文字显示
     */
    private TextView tv_hintText;
    /**
     * 录制时间显示
     */
    private TextView tv_time;
    /**
     * 录制按钮
     */
    private ImageView imgbtn_record;
    /**
     * 录制抖动效果显示的View
     */
    private FluctuateView mFluctuateView1;
    private FluctuateView mFluctuateView2;
    /**
     * 用户当前按下的X坐标
     */
    private float currenX;
    /**
     * 用户当前按钮下的Y坐标
     */
    private float currenY;

    /**
     * 试听按钮中心点的X坐标
     */
    private float auditionX;
    /**
     * 试听按钮中心点的X坐标
     */
    private float auditionY;
    /**
     * 删除按钮中心带你的X坐标
     */
    private float deleteX;
    /**
     * 删除按钮中心带你的Y坐标
     */
    private float deleteY;
    /**
     * 试听按钮和录制按钮之间的距离
     */
    private double distance1;
    /**
     * 删除按钮和录制按钮之间的距离
     */
    private double distance2;
    /**
     * 删除按钮的宽度
     */
    private int delteBtnWidth;
    /**
     * 删除按钮的高度
     */
    private int delteBtnHeight;
    /**
     * 试听按钮的宽度
     */
    private int auditionBtnWidth;
    /**
     * 试听按钮的高度
     */
    private int auditionBtnHeight;

    //判断第一次进来
    private boolean isflag = true;

    /**
     * 录制状态
     */
    public enum RecordStatus {
        PREPARE_RECOIRD,
        STARTE_RECORD,
        CANCLE_RECORD
    }

    /**
     * 默认是准备录制状态
     */
    private RecordStatus Status = RecordStatus.PREPARE_RECOIRD;

    /**
     * 当前录制的时间
     */
    private int currentTime;
    /**
     * 更新时间
     */
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == UPDATETIME) {
                if (Status == RecordStatus.STARTE_RECORD) {
                    currentTime += 1000;
                    tv_time.setText(showTimeCount(currentTime));
                    mHandler.sendEmptyMessageDelayed(UPDATETIME, 1000);
                }
            }
        }
    };

    /**
     * 设置监听器
     *
     * @param mOnRecordStatusListener
     */
    public void setOnRecordStatusListener(onRecordStatusListener mOnRecordStatusListener) {
        this.mOnRecordStatusListener = mOnRecordStatusListener;
    }

    public AudioRecordLayout(Context context) {
        super(context);
    }

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

    public AudioRecordLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //获取播放View
        mAuditionBtn = (AuditionButton) getChildAt(1);
        mDeleteVoiceBtn = (AuditionButton) getChildAt(2);
        tv_hintText = (TextView) findViewById(R.id.tv_hint);
        imgbtn_record = (ImageView) findViewById(R.id.imgbtn_record);
        mFluctuateView1 = (FluctuateView) findViewById(R.id.fluctuate_view1);
        mFluctuateView2 = (FluctuateView) findViewById(R.id.fluctuate_view2);
        tv_time = (TextView) findViewById(R.id.tv_time);
        onMeasureWidthAndHeight();

    }


    /**
     * 测量各控件宽度和高度
     */
    public void onMeasureWidthAndHeight() {
        //获取删除按钮宽和高
        ViewTreeObserver vto = mDeleteVoiceBtn.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mDeleteVoiceBtn.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                delteBtnWidth = mDeleteVoiceBtn.getWidth();
                delteBtnHeight = mDeleteVoiceBtn.getHeight();
            }
        });
        //获取试听按钮的宽和高
        ViewTreeObserver vto1 = mAuditionBtn.getViewTreeObserver();
        vto1.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mAuditionBtn.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                auditionBtnWidth = mAuditionBtn.getWidth();
                auditionBtnHeight = mAuditionBtn.getHeight();
            }
        });
    }


    /**
     * 获取各按钮中心点的坐标
     */
    public void onCenterCoordinates() {
        //获取试听按钮中心点的坐标
        auditionX = mAuditionBtn.getX() + auditionBtnWidth / 2;
        auditionY = mAuditionBtn.getY() + auditionBtnHeight / 2;
        //获取删除按钮中心点的坐标
        deleteX = mDeleteVoiceBtn.getX() + delteBtnWidth / 2;
        deleteY = mDeleteVoiceBtn.getY() + delteBtnHeight / 2;
    }

    /**
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        currenX = event.getX();
        currenY = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //判断当前按下的坐标是否在按钮范围里面
                if (containRecordBtn()) {
                    //开始录制
                    Status = RecordStatus.STARTE_RECORD;
                    updateButton();
                    tv_time.setText("00:00");
                    mHandler.sendEmptyMessageDelayed(UPDATETIME, 1000);
                    if (mOnRecordStatusListener != null) {
                        mOnRecordStatusListener.onRecordStart();
                    }
                    if (isflag) {
                        onCenterCoordinates();
                        distance1 = getCoordinateDistance(auditionX, auditionY, currenX, currenY);
                        distance2 = getCoordinateDistance(currenX, currenY, deleteX, deleteY);
                        isflag = false;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (Status == RecordStatus.STARTE_RECORD) {
                    //松开
                    if (containDeleteView()) {
                        tv_hintText.setVisibility(VISIBLE);
                        hideTimeLayout();
                        tv_hintText.setText("松开取消发送");
                    } else if (containPlayView()) {
                        tv_hintText.setVisibility(VISIBLE);
                        hideTimeLayout();
                        tv_hintText.setText("松开试听");
                    } else {
                        tv_hintText.setVisibility(GONE);
                        showTimeLayout();
                        tv_time.setText(showTimeCount(currentTime));
                    }
                    double distance = getCoordinateDistance(auditionX, auditionY, currenX, currenY);
                    mAuditionBtn.setDistance(distance / distance1);

                    double distance1 = getCoordinateDistance(currenX, currenY, deleteX, deleteY);
                    mDeleteVoiceBtn.setDistance(distance1 / distance2);
                }
                break;
            case MotionEvent.ACTION_UP:
                Status = RecordStatus.CANCLE_RECORD;
                resetTime();
                if (containDeleteView()) {
                    if (mOnRecordStatusListener != null) {
                        mOnRecordStatusListener.onRecordCancle();
                    }
                } else if (containPlayView()) {
                    Log.v("test", "试听");
                } else {
                    if (mOnRecordStatusListener != null) {
                        mOnRecordStatusListener.onRecordComplete();
                    }
                }
                updateButton();
                mAuditionBtn.setSelected(false);
                mDeleteVoiceBtn.setSelected(false);
        }
        return true;
    }

    /**
     * 隐藏录制时间布局
     */
    public void hideTimeLayout() {
        tv_time.setVisibility(GONE);
        mFluctuateView1.setVisibility(GONE);
        mFluctuateView2.setVisibility(GONE);
    }


    /**
     * 显示录制时间布局
     */
    public void showTimeLayout() {
        tv_time.setVisibility(VISIBLE);
        mFluctuateView1.setVisibility(VISIBLE);
        mFluctuateView2.setVisibility(VISIBLE);
    }


    /**
     * 松开按钮重置时间
     */
    public void resetTime() {
        mHandler.removeMessages(UPDATETIME);
        tv_hintText.setText("00:00");
        currentTime = 0;
    }

    /**
     * 更新显示
     */
    public void updateButton() {
        if (Status == RecordStatus.STARTE_RECORD) {
            mAuditionBtn.setVisibility(VISIBLE);
            mDeleteVoiceBtn.setVisibility(VISIBLE);
            //波动效果
            showTimeLayout();
            mFluctuateView1.start();
            mFluctuateView2.start();
            tv_hintText.setVisibility(GONE);
        } else {
            tv_hintText.setVisibility(VISIBLE);
            tv_hintText.setText("按住说话");
            //试听功能
            mAuditionBtn.setDistance(1f);
            mAuditionBtn.setVisibility(GONE);
            //删除功能
            mDeleteVoiceBtn.setDistance(1f);
            mDeleteVoiceBtn.setVisibility(GONE);
            //波动效果
            mFluctuateView1.stop();
            mFluctuateView2.stop();
            hideTimeLayout();
        }
    }

    /**
     * 获取二个坐标点之间的距离
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return
     */
    public double getCoordinateDistance(float x1, float y1, float x2, float y2) {
        return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }

    /**
     * 判断当前当前按住的点是否在录制按钮里面
     *
     * @return
     */
    public boolean containRecordBtn() {
        if ((imgbtn_record.getX() < currenX && (imgbtn_record.getX() + imgbtn_record.getWidth()) > currenX)
                && (imgbtn_record.getY() < currenY && (imgbtn_record.getY() + imgbtn_record.getHeight()) > currenY)) {
            return true;
        }
        return false;
    }

    /**
     * 判断当前滑动范围是否在试听按钮里面
     */
    public boolean containPlayView() {
        if ((mAuditionBtn.getX() < currenX && (mAuditionBtn.getX() + mAuditionBtn.getWidth()) > currenX)
                && (mAuditionBtn.getY() < currenY && (mAuditionBtn.getY() + mAuditionBtn.getHeight()) > currenY)) {
            tv_hintText.setText("松手试听");
            mAuditionBtn.setSelected(true);
            return true;
        }
        mAuditionBtn.setSelected(false);
        return false;
    }


    /**
     * 判断当前滑动范围是否在删除里面
     */
    public boolean containDeleteView() {
        if ((mDeleteVoiceBtn.getX() < currenX && (mDeleteVoiceBtn.getX() + mDeleteVoiceBtn.getWidth()) > currenX)
                && (mDeleteVoiceBtn.getY() < currenY && (mDeleteVoiceBtn.getY() + mDeleteVoiceBtn.getHeight()) > currenY)) {
            mDeleteVoiceBtn.setSelected(true);
            return true;
        }
        mDeleteVoiceBtn.setSelected(false);
        return false;
    }


    /**
     * 转换时间
     *
     * @param time
     * @return
     */
    public String showTimeCount(long time) {
        if (time >= 360000000) {
            return "00:00:00";
        }
        String timeCount = "";
        long hourc = time / 3600000;
        String hour = "0" + hourc;
        hour = hour.substring(hour.length() - 2, hour.length());

        long minuec = (time - hourc * 3600000) / (60000);
        String minue = "0" + minuec;
        minue = minue.substring(minue.length() - 2, minue.length());

        long secc = (time - hourc * 3600000 - minuec * 60000) / 1000;
        String sec = "0" + secc;
        sec = sec.substring(sec.length() - 2, sec.length());
        timeCount = minue + ":" + sec;
        return timeCount;
    }

    /**
     * 录制状态监听器
     */
    public interface onRecordStatusListener {

        /**
         * 录制开始
         */
        void onRecordStart();

        /**
         * 录制完成
         */
        void onRecordComplete();

        /**
         * 录制取消
         */
        void onRecordCancle();

    }



attrs:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="line_count" format="integer" />
    <attr name="line_color" format="color" />
    <attr name="line_spacing" format="dimension" />
    <attr name="line_width" format="dimension" />

    <declare-styleable name="FluctuateView">
        <attr name="line_count" />
        <attr name="line_color" />
        <attr name="line_spacing" />
        <attr name="line_width"></attr>
    </declare-styleable>
</resources>



 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray"
    android:orientation="vertical">

    <com.lly.view.AudioRecordLayout
        android:id="@+id/record_layout"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_alignParentBottom="true"
        android:background="@android:color/white">

        <ImageView
            android:id="@+id/imgbtn_record"
            android:layout_width="90dp"
            android:layout_height="90dp"
            android:layout_centerInParent="true"
            android:src="@mipmap/lab" />

        <com.view.AuditionButton
            android:id="@+id/img_playView"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:scaleType="centerInside"
            android:src="@mipmap/skin_aio_audio_panel_listen_nor"
            android:visibility="invisible" />

        <com.view.AuditionButton
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="10dp"
            android:scaleType="centerInside"
            android:src="@mipmap/skin_aio_audio_panel_del_nor"
            android:visibility="invisible" />

        <TextView
            android:id="@+id/tv_hint"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/imgbtn_record"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="10dp"
            android:text="按住说话"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textSize="16sp"
            android:visibility="visible" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/imgbtn_record"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="10dp"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/color1"
            android:textSize="16sp"
            android:visibility="gone" />

        <com.lly.view.FluctuateView
            android:id="@+id/fluctuate_view2"
            android:layout_width="wrap_content"
            android:layout_height="16dp"
            android:layout_alignTop="@+id/tv_time"
            android:layout_marginLeft="8dp"
            android:layout_toRightOf="@+id/tv_time"
            android:visibility="gone"
            app:line_color="#3bb7e9"
            app:line_count="8"
            app:line_spacing="4dp"
            app:line_width="1dp" />

        <com.lly.view.FluctuateView
            android:id="@+id/fluctuate_view1"
            android:layout_width="wrap_content"
            android:layout_height="16dp"
            android:layout_alignTop="@+id/tv_time"
            android:layout_marginRight="8dp"
            android:layout_toLeftOf="@+id/tv_time"
            android:visibility="gone"
            app:line_color="#3bb7e9"
            app:line_count="8"
            app:line_spacing="4dp"
            app:line_width="1dp" />
    </com.lly.view.AudioRecordLayout>

</RelativeLayout>



Demo地址:下载地址


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值