Android模仿华为长按功能键实现清除内存功能

经常使用华为手机的朋友一定有用到过华为系统,长按右下角菜单键,如果内存可以清除,就会出现一个上拉清除内存的功能界面。之前博客里也提到了,f一直想做出这个效果,琢磨了一段时间,基本做出了雏形,不过做的只是下拉,圆弧从没有到完整闭合的效果,没有融入属性动画(华为系统默认效果有个类似皮球落地反复弹跳的动画),f本身对于动画不感冒,所以没有写进去,如果有人感兴趣,可以在我的基础上添加,同时f也没把具体清除的内存写进去,因为是调用系统的一些简单功能,大家有兴趣可以自己查一下。f旨在学习一下自定义view,viewgroup。这里先膜拜鸿洋大神,他的自定义view我已经连续学习了好多天了,还会一直坚持下去。下面正文开始。


1.简单分析一下,手指必须是触碰最下边的布局,才能实现清除功能,所以我们打算给整个view或者是viewgroup设置onTouchListener,往上拉的时候,圆弧开始出现,当向上滑动的距离,等于圆的直径时,圆弧出现一半,等于两倍直径时,圆弧闭合成为一个完整的圆。超过两倍直径再上拉不会动,松手就清除了内存,如果未超过两倍直径,布局会回去,同时圆弧也消失。
2.第一步,我们先画圆弧。

public class MyViewOne extends View{

    private int mLastY;
    private int mScreenHeight,mScreenWidth;
    private RectF rectF;
    public static final int mRadius = 50;
    private Paint paint;
    private int startHeight = 50;
    public float sweepAngle = 0;
    private boolean clear;
    private int mStrokenWidth = 2;
    private int viewHeight;


    public MyViewOne(Context context, AttributeSet attrs,float sweepAngle) {
        super(context, attrs);
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        mScreenHeight = outMetrics.heightPixels;
        mScreenWidth = outMetrics.widthPixels;
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(mStrokenWidth);//设置画笔末端的宽度
        paint.setStrokeCap(Paint.Cap.ROUND);//设置画笔末端是圆角
        this.sweepAngle = sweepAngle;//圆弧的闭合角度
        Log.i("abc", "sweepAngle:"+sweepAngle);


    }

    public MyViewOne(Context context) {
        this(context, null,0f);

    }

    public MyViewOne(Context context,float sweepAngle){
        this(context,null,sweepAngle);
    }

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

    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left,  top,  right,  bottom);
    }

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        rectF = new RectF(mScreenWidth/2 - mRadius,startHeight,mScreenWidth/2+mRadius,2*mRadius+startHeight);
        canvas.drawArc(rectF, -90, sweepAngle, false, paint);

    }

这里需要一些自定义view的基础,以及如何画圆弧。需要提醒一下,drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)画圆弧时候,startAngle是圆弧开始的角度,以三点钟为0,六点钟是90,九点钟是180,12点钟是-90,顺时针画弧,sweepAngle是圆弧的角度,也就是开始于结束的角度差,注意是差值,超过360就是圆。


3.分析华为的系统功能,有两段文本提示内存的相关消息,所以我画了一个布局,准备在自定义viewgroup时候,add上去。

public class MyViewGroup extends LinearLayout{

    MyViewOne view;
    private Context context;
    private TextView tvMemory,tvPullToClear;
    private UpdateMemoryListener updateMemoryListener;

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

    public MyViewGroup(final Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        View child = LayoutInflater.from(context).inflate(R.layout.progress , null);
        viewHeight = child.getMeasuredHeight();
        tvMemory = (TextView) child.findViewById(R.id.tv_memory);
        tvPullToClear = (TextView) child.findViewById(R.id.tv_pull_to_clear);
        addView(child);
    }

public void update(float sweepAngle , int dy ){
        if(null != getChildAt(1)){
            removeViewAt(1);
        }//每次加之前,需要把上一个删掉。由于整体是动态add上去的,第一个add的child,下标为0,后面add的MyViewOne下标为1
        //if(updateMemoryListener != null){}需要重写的方法
        view = new MyViewOne(context,null,sweepAngle);
        LinearLayout.LayoutParams lp = new    LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);
        lp.topMargin = 30;
        view.setLayoutParams(lp);
        addView(view);
        scrollTo(0, -dy);//后面做解析
}

public interface UpdateMemoryListener{
        public String updateAvailableMemory();

        public String getTotalMemory();
    }

    public void setUpdateMemoryListener(UpdateMemoryListener updateMemoryListener){
        this.updateMemoryListener = updateMemoryListener;
    }

UpdateMemoryListener这个接口主要是提供内存信息的接口,可以在activity里,给MyViewGroup设置UpdateMemoryListener监听,重写方法,同时要修改一下update方法。我没有用,所以注掉了。
继承自线性布局,所以需要我们指定布局的排列方法。如果是垂直排列,那么布局就是往下面加,所以后面addview就是加到下面的。
下面贴progress的布局文件

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

    <TextView
        android:layout_marginTop="10dip"
        android:id="@+id/tv_memory"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_below="@+id/tv_memory"
        android:id="@+id/tv_pull_to_clear"
        android:layout_marginTop="10dip"
        android:gravity="center_horizontal"
        android:text="向下滑动,清除全部应用"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />


</RelativeLayout>

4.然后怎么用呢?就是写到布局文件,加载到activity中。下面是布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#e0000000"
    android:id="@+id/rl"
    >

    <com.fjf.pulltoclear.MyViewGroup
        android:orientation="vertical"
        android:id="@+id/my_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></com.fjf.pulltoclear.MyViewGroup>

</RelativeLayout>

下面是activity

public class MainActivity extends Activity {

    private MyViewGroup view;
    private int mLastY;
    private RelativeLayout rl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_pulltoclear);
        initViews();
    }

    private void initViews(){
        rl = (RelativeLayout) findViewById(R.id.rl);
        rl.getBackground().setAlpha(100);
        view = (MyViewGroup) findViewById(R.id.my_view);
        view.setUpdateMemoryListener(new UpdateMemoryListener() {

            @Override
            public String updateAvailableMemory() {
                // TODO Auto-generated method stub
                return null;
            }

            @Override
            public String getTotalMemory() {
                // TODO Auto-generated method stub
                return null;
            }
        });

        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                int y = (int) event.getY();
                switch (action) {
                case MotionEvent.ACTION_DOWN:
                    mLastY = y;
                    return true;
                case MotionEvent.ACTION_MOVE:
                    int dy = y - mLastY;//获得move的距离
                    Log.i("abc", "dy:" + dy);
                    //向下拉
                    if (dy > 0) {
                    //如果下拉的距离未超过两倍直径,即四倍半径
                        if (dy < 4 * MyViewOne.mRadius) {
                            view.update(1.8f * dy, dy);
                        } else {
                    //超过了,就显示一个完整的圆     
                    view.update(360f, 4 * MyViewOne.mRadius);
                        }
//如果往上拉,则不动
                    } else if (dy <= 0) {
                        view.update(0f, 0);
                    }
                    return true;
                case MotionEvent.ACTION_UP:
                //同样判断是下拉
                    if (y - mLastY > 0) {
                    //如果未超过四倍半径,不清理内存,同时布局回到原位,圆弧要消失
                        if (y - mLastY < 4 * MyViewOne.mRadius) {
                            Log.i("abc", "不清除内存");
                            view.update(0f, 0);
                            //否则清除内存,关闭当前界面
                        } else {
                            Log.i("abc", "清除内存");

                            Toast.makeText(MainActivity.this, "内存已经释放", Toast.LENGTH_SHORT).show();
                            finish();
                        }
                    }
                    return true;
                }
                return false;
            }
        });
    }
}   

注意如果要提醒内存,注意UpdateMemoryListener的实现
touch事件的注解也比较详细,现在解析一下MyViewGroup的update()方法,重点说明滑动和闭合圆弧的角度。

华为系统的实现,滑动距离为直径时候,显示一半圆,滑动距离为两倍直径,显示完整的圆。两种情况,直径与圆弧的比例分别为R:180,2R:360,所以距离:弧度=R:180,我们定了圆的半径为50,所以弧度=180*距离/R,也就是弧度=1.8×距离。至于scroll,更简单了,只调用最基本的api即可,因为是向下滑动,所以scroll传入的值为负即向下滑动,而x轴保持不变,所以调用scrollTo( 0 , -距离);即可,注意,我们向下滑才出发update方法,下滑的距离为正。

基本的解析就是这些,接下来要分享一下f完成这个功能时候遇到的一个问题。

5.问题总结
f最开始,把事件分发写到了MyViewOne中,而progress布局中的textview是写到mainactivity的布局中,这样的结果是整个界面在滑动的过程中一直抖动,一直抖,当初是用postInvalidate()方法实现重绘的,f分析认为,可能是由于这些子控件不是一个view或者viewgroup,如果把他们封装到一起,可能就不会抖动。当然现在这种方法和每次根据传入的角度new一个view添加上,我认为这种处理,对于一直抖动的情况可能有效。

而且比较2的是,继承自线性布局,我却没有指定方法,圆弧一直出不来,快郁闷死了,后来去看线性布局的源码才意识到没有指定方向啊。。。

f研究这个控件有一段时间,最开始脑袋有点乱,等f把单独的功能分开以后,有种豁然开朗的感觉,也算是经验吧,对于复杂的内容,分割开,一块块解决,可能更顺利一些。

f也深入学习了鸿洋的自定义view,深入去理解scroller的一些用法,收获真的很多,搞技术还是需要多看多了解啊,见多识广,遇到问题才能分析解决。

博客到这里也算告一段落了吧,有问题可以留言,希望能和大家多多交流~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值