Android录制按钮源码解析

本文实例为大家分享了Android实现录制按钮的具体代码,供大家参考,具体内容如下

初始化

布局文件中参数

private void initParame(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RButtonY, defStyleAttr, 0);
    //外圆和内部正方形之间的间距
    mCircleOutMarginSize = typedArray.getDimensionPixelSize(R.styleable.RButtonY_rby_circle_out_margin, 5);
    //外圆画笔的宽度
    mCircleWidth = typedArray.getDimensionPixelSize(R.styleable.RButtonY_rby_circle_width, 5);
    //外圆画笔的颜色
    mCirclePaintColor = typedArray.getColor(R.styleable.RButtonY_rby_circle_paint_color, Color.YELLOW);
    //内部正方形画笔的颜色
    mRectPaintColor = typedArray.getColor(R.styleable.RButtonY_rby_rect_paint_color, Color.RED);
    //内部正方形初始边长相对于外圆内切正方形边长比率 0-1
    mRectRateStart = typedArray.getFloat(R.styleable.RButtonY_rby_rect_rate_start, 0.9f);
    //内部正方形结束边长相对于外圆内切正方形边长比率 0-1
    mRectRateFinish = typedArray.getFloat(R.styleable.RButtonY_rby_rect_rate_fnish, 0.5f);
    //录制规定最短时间
    mShortest = typedArray.getInteger(R.styleable.RButtonY_rby_short_time, 3);
    //录制规定最长时间
    mLongest = typedArray.getInteger(R.styleable.RButtonY_rby_long_time, 10);
    typedArray.recycle();
}

画笔初始化

// Paint.Style.FILL设置只绘制图形内容
// Paint.Style.STROKE设置只绘制图形的边
// Paint.Style.FILL_AND_STROKE设置都绘制
private void initPaint() {
    //外圆画笔
    mCirclePaint = new Paint();
    mCirclePaint.setAntiAlias(true);
    mCirclePaint.setColor(mCirclePaintColor);
    mCirclePaint.setStyle(Paint.Style.STROKE);
    mCirclePaint.setStrokeWidth(mCircleWidth);
    //内部正方形画笔
    mRectPaint = new Paint();
    mRectPaint.setAntiAlias(true);
    mRectPaint.setColor(mRectPaintColor);
    mRectPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}

内部正方形RectF初始化

private void initRect() {
        mRectF = new RectF();
}

内部正方形所需动画初始化, 当开始录制或者结束录制时候,内部正方形会有一个动画效果,这个动画效果需要内部正方形边长改变才能实现.

/**
 * 初始化动画
 * 这里对动画进行监听, 获取正方形边长随动画改变的值,然后重绘
 */
private void initAnimator() {
    mAnimator = new ValueAnimator();
    /**
     * onAnimationStart() - 当动画开始的时候调用.
     * onAnimationEnd() - 动画结束时调用.
     * onAnimationRepeat() - 动画重复时调用.
     * onAnimationCancel() - 动画取消时调用.取消动画也会调用onAnimationEnd,它不会关系动画是怎么结束的。
     */
    mAnimator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            //动画结束
            isAnimRuning = false;
        }
        @Override
        public void onAnimationStart(Animator animation) {
            //动画开始
            isAnimRuning = true;
        }
    });
    //动画进度监听,获取正方形随动画变化的边长,然后重绘
    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //动态获取正方形边长
            mTempRectSize = (float) animation.getAnimatedValue();
            invalidate();//重绘
        }
    });
}

确定圆形半径,圆心坐标,内部正方形边长等

 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     super.onLayout(changed, left, top, right, bottom);
     int width = getWidth();
     int height = getHeight();
     //圆心坐标
     centerX = width / 2;
     centerY = height / 2;
     //半径
     radius = Math.min(centerX, centerY) - mCircleOutMarginSize / 2;
     //pow 平方,sqrt 开方
     //正方形开始边长,圆形直径的平方除以二再开放,为正方形边长.
     mRectStartSize = (int) (Math.sqrt(Math.pow(radius * 2, 2) / 2) * mRectRateStart);
     //正方形结束边长
     mRectEndSize = (int) (mRectStartSize * mRectRateFinish);
    //mTempRectSize == 0 时, 即第一创建该View.
    if (mTempRectSize == 0) {
        //如果屏幕旋转,onLayout将被回调,此时并不希望mTempRectSize被重新赋值为mRectStartSize(开始状态).
        //所以只有当第一次创建时,才需要为mTempRectSize赋值为mRectStartSize(开始状态)
        mTempRectSize = mRectStartSize;
    }
 }

绘制内部正方形和外圆

@Override
 protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     //外圆绘制
     canvas.drawCircle(centerX, centerY, radius, mCirclePaint);
     //正方形四点坐标
     int mLeftRectTemp = (int) (centerX - mTempRectSize / 2);
     int mRightRectTemp = (int) (centerX + mTempRectSize / 2);
     int mTopRectTemp = (int) (centerY + mTempRectSize / 2);
     int mButtonRectTemp = (int) (centerY - mTempRectSize / 2);
     //绘制正方形
     mRectF.set(mLeftRectTemp, mTopRectTemp, mRightRectTemp, mButtonRectTemp);
     //(float) Math.sqrt(radius): 圆角半径
     canvas.drawRoundRect(mRectF, (float) Math.sqrt(radius), (float) Math.sqrt(radius), mRectPaint);
 }

录制开始和结束方法以及动画执行方法

/**
 * 录制开始
 */
private void recordStart() {
    //正方形开始动画
    startAnimation(mRectStartSize, mRectEndSize);
    if (rbyCb != null) {
        //录制开始的回调
        rbyCb.startCb(String.valueOf(mCurrent));
    }
    //开始计时
    mHandler.sendEmptyMessage(0);
    //录制标识为开始
    up = true;
    mTempRectSize = mRectEndSize;
}
/**
 * 录制结束
 */
private void recordFinish() {
    //正方形结束动画
    startAnimation(mRectEndSize, mRectStartSize);
    if (rbyCb != null) {
        //结束时回调
        rbyCb.finishCb(String.valueOf(mCurrent));
    }
    //录制结束,当前时间归0
    mCurrent = 0;
    mHandler.removeCallbacksAndMessages(null);
    //录制标识为结束
    up = false;
    mTempRectSize = mRectStartSize;
}
/**
 * 开始动画
 *
 * @param startValue
 * @param endValue
 */
private void startAnimation(float startValue, float endValue)
    mAnimator.setFloatValues(startValue, endValue);
    mAnimator.setDuration(100);
    mAnimator.setInterpolator(new LinearInterpolator());
    mAnimator.start();
}

回调接口

public interface RBYCallback {
    /**
     * 记录结束的回调
     *
     * @param current
     */
    void finishCb(String current);
    /**
     * 每一秒 都会触发该回调
     *
     * @param current
     */
    void eventCb(String current);
    /**
     * 开始记录的回调
     */
    void startCb(String current);
    /**
     * 录制时长小于录制最短要求时间之时,用户点击按钮时候,回调该方法
     */
    void lessShortTimeRecode(String current);
}

对控件点击事件进行处理

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_UP:
            //如果正方形动画正在播放,就拒绝按钮点击
            if (isAnimRuning) return true;
            //up为false代表未开始记录,true 代表开始记录
            //未开始记录时,mCurrent是等于0
            if (!up && mCurrent == 0) {
                recordStart();
            }
            //已开始记录,并且当前录制时间大于或者等于所设置的最短记录时长,则按钮可以手动结束
            if (up && mCurrent >= mShortest) {
                recordFinish();
            }
            //已开始记录,当前录制时间小于所设置的最短记录时长,并且录制时间大于1,则回调方法通知当前还不能手动结束录制
            if (up && mCurrent < mShortest && mCurrent >= 1) {
                if (rbyCb != null) {
                    rbyCb.lessShortTimeRecode(String.valueOf(mCurrent));
                }
            }
            break;
    }
    return true;//消费事件
}

屏幕旋转保存与还原数据

//屏幕旋转时候保存必要的数据
@Nullable
@Override
protected Parcelable onSaveInstanceState() {
    if (mCurrent != 0) {
        Bundle bundle = new Bundle();
        //保存系统其他原有的状态信息
        bundle.putParcelable("instance", super.onSaveInstanceState());
        //保存当前的一些状态
        bundle.putFloat("rect_size", mTempRectSize);//保存方形边长
        bundle.putBoolean("up", up);//当前录制状态
        bundle.putInt("mCurrent", mCurrent);//当前录制时间
        return bundle;
    } else {
        return super.onSaveInstanceState();
    }
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
    //判断state的类型是否为bundle,若是则从bundle中取数据
    if (state instanceof Bundle) {
        Bundle bundle = (Bundle) state;
        mTempRectSize = bundle.getFloat("rect_size");
        up = bundle.getBoolean("up");
        mCurrent = bundle.getInt("mCurrent");
        //开始计时
        mHandler.sendEmptyMessage(0);
        super.onRestoreInstanceState(bundle.getParcelable("instance"));
        return;
    }
    super.onRestoreInstanceState(state);
}

定时mHandler

@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        mCurrent++;
        if (rbyCb != null) {
            rbyCb.eventCb(String.valueOf(mCurrent));
        }
        if (mCurrent >= mLongest) {//当前记录时间大于或等于最大记录时间,将自动结束记录
            recordFinish();
        } else {
            mHandler.sendEmptyMessageDelayed(0, 1000);
        }
    }
};

页面销毁处理

//页面销毁,清空消息,防止内存泄漏
@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    mHandler.removeCallbacksAndMessages(null);
    mHandler = null;
}

效果图

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android LayoutTransiton实现简单的录制按钮

    最近公司要做的项目中要求实现一个简单的视频录制功能的组件,我简单设计了一个,主要功能就是开始,暂停,停止和显示录制时间长度.首先看一下效果图: 可以看到是一个非常简单的动画效果,为了方便使用,我把他做成了aar并发布到了jCenter,集成方式: compile 'com.rangaofei:sakarecordview:0.0.2' 组件里用到的库也非常简单,包括databinding,属性动画和layouttransition.通过这个简单的库简单的介绍一下LayoutTransition的

  • Android录制按钮源码解析

    本文实例为大家分享了Android实现录制按钮的具体代码,供大家参考,具体内容如下 初始化 布局文件中参数 private void initParame(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RButtonY, defStyleAttr, 0); //外

  • Android自定义开关按钮源码解析

    本文实例为大家分享了Android自定义开关的具体代码,供大家参考,具体内容如下 以 ToggleColorY 为例分析, ToggleImageY逻辑代码差不多 初始化参数 获取背景颜色,按钮颜色,开关状态 @SuppressLint("ResourceAsColor") private void initParame(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { TypedArray typed

  • Android 中 SwipeLayout一个展示条目底层菜单的侧滑控件源码解析

    由于项目上的需要侧滑条目展示收藏按钮,记得之前代码家有写过一个厉害的开源控件 AndroidSwipeLayout 本来准备直接拿来使用,但是看过 issue 发现现在有不少使用者反应有不少的 bug ,而且代码家现在貌似也不进行维护了.故自己实现了一个所要效果的一个控件.因为只是实现我需要的效果,所以大家也能看到,代码里有不少地方我是写死的.希望对大家有些帮助.而且暂时也不需要 AndroidSwipeLayout 大而全的功能,算是变相给自己做的项目精简代码了. 完整示例代码请看:GitHu

  • Android Handler,Message,MessageQueue,Loper源码解析详解

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文< Android中Handler的使用>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能.但

  • Android音视频开发Media FrameWork框架源码解析

    目录 一.Media FrameWork背景 二.Media Framework“路线图” 2.1 代理端 2.2 服务端 2.2.1 Source 2.2.2 Decoder 2.2.3 Renderer 2.2.4 Foundation 2.3 OMX端 2.4 Kernel端 三.media播放的流程 四.Media FrameWork源码分析 一.Media FrameWork背景 Media Framework (媒体函数库):此函数库让Android 可以播放与录制许多常见的音频与视

  • Android Jetpack 组件LiveData源码解析

    目录 前言 基本使用 疑问 源码分析 Observer ObserverWrapper LifecycleBoundObserver MutableLiveData postValue setValue 问题答疑 LiveData 特性引出的问题 问题解决 最后 前言 本文来分析下 LiveData 的源码,以及其在实际开发中的一些问题. 基本使用 一般来说 LiveData 都会配合 ViewModel 使用,篇幅原因关于 ViewModel 的内容将在后续博客中分析,目前可以将 ViewMo

  • Android源码解析之截屏事件流程

    今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程.用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了).那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程. 我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电

  • Android okhttp的启动流程及源码解析

    前言 这篇文章主要讲解了okhttp的主要工作流程以及源码的解析. 什么是OKhttp 简单来说 OkHttp 就是一个客户端用来发送 HTTP 消息并对服务器的响应做出处理的应用层框架. 那么它有什么优点呢? 易使用.易扩展. 支持 HTTP/2 协议,允许对同一主机的所有请求共用同一个 socket 连接. 如果 HTTP/2 不可用, 使用连接池复用减少请求延迟. 支持 GZIP,减小了下载大小. 支持缓存处理,可以避免重复请求. 如果你的服务有多个 IP 地址,当第一次连接失败,OkHt

  • Android文件存储SharedPreferences源码解析

    1.我们都知道SharedPreferences 是android可以用来存放key value的的文件. SharedPreferences sp = getSharedPreferences("fileName", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("key","value"); editor.commit(

  • Android源码解析onResume方法中获取不到View宽高

    目录 前言 问题1.为什么onCreate和onResume中获取不到view的宽高? 问题2.为什么View.post为什么可以获取View宽高? 结论 前言 有一个经典的问题,我们在Activity的onCreate中可以获取View的宽高吗?onResume中呢? 对于这类八股问题,只要看过都能很容易得出答案:不能. 紧跟着追问一个,那为什么View.post为什么可以获取View宽高? 今天来看看这些问题,到底为何? 今日份问题: 为什么onCreate和onResume中获取不到vie

随机推荐