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的一些用法,收穫真的很多,搞技術還是需要多看多瞭解啊,見多識廣,遇到問題才能分析解決。

博客到這裏也算告一段落了吧,有問題可以留言,希望能和大家多多交流~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章