当前位置: 代码迷 >> Android >> Android仿照华为长按功能键实现清除内存功能
  详细解决方案

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

热度:73   发布时间:2016-04-27 23:18:57.0
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的一些用法,收获真的很多,搞技术还是需要多看多了解啊,见多识广,遇到问题才能分析解决。

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案