現在手機上的懸浮窗應用越來越多,對用戶來說,最常見的懸浮窗應用就是安全軟件的懸浮小控件,拿360衛士來說,當開啟懸浮窗時,它是一個小球,小球可以拖動,當點擊小球出現大窗體控件,可以進行進一步的操作如:釋放手機內存等等。于是借著慕課網的視頻,仿著實現了360加速球,增加了點擊小球進行釋放內存的功能。
由于是手機只有頻幕截圖:實現后如下圖所示:點擊開啟按鈕,出現懸浮窗小球控件上面顯示手機的可用內存百分比;當拖動小球時,小球變為Android圖標;松開小球,小球依附在頻幕兩側;點擊小球,手機底部出現大窗體控件,點擊里面的小球,進行手機內存的釋放;點擊手機屏幕的其他區域,大窗體消失,小球重新出現。
效果如下:


接下來就是實現的一些重要步驟:
1.FloatCircleView的實現(自定義view)
實現FloatCircleView的過程就是自定義view的過程。1、自定義View的屬性 2、在View的構造方法中獲得我們自定義的屬性 3、重寫onMesure 4、重寫onDraw。我們沒有自定義其他屬性所以省了好多步驟。
各種變量的初始化,設置拖動小球時要顯示的圖標,已經計算各種內存。(用于顯示在小球上)
public int width=100; public int heigth=100; private Paint circlePaint;//畫圓 private Paint textPaint; //畫字 private float availMemory; //已用內存 private float totalMemory; //總內存 private String text; //顯示的已用內存百分比 private boolean isDraging=false; //是否在拖動狀態。 private Bitmap src; private Bitmap scaledBitmap; //縮放后的圖片。 /** * 初始化畫筆以及計算可用內存,總內存,和可用內存百分比。 */ public void initPatints() { circlePaint = new Paint(); circlePaint.setColor(Color.CYAN); circlePaint.setAntiAlias(true); textPaint = new Paint(); textPaint.setColor(Color.WHITE); textPaint.setTextSize(25); textPaint.setFakeBoldText(true); textPaint.setAntiAlias(true); //設置圖片 src = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); //縮放后的圖片(將圖標設置的和懸浮小球一樣大小。) scaledBitmap = Bitmap.createScaledBitmap(src, width, heigth, true); //計算已用內存,總內存,已用內存百分比, availMemory= (float) getAvailMemory(getContext()); totalMemory= (float) getTotalMemory(getContext()); text=(int)((availMemory/totalMemory)*100)+"%"; }onMeasure();就是將固定的寬高寫死,通過 setMeasuredDimension(width, heigth);傳入。
onDraw();進行懸浮小球繪制。定義一個boolean變量判斷當前狀態是否為拖動小球狀態,如果是拖動小球狀態,就在該位置繪制android圖標,如果不是拖動狀態,就進行小球繪制。畫小球沒有難度,關鍵是畫字。下面的2個圖可以加深對畫字時的理解。

1.畫字時的x坐標(1.textPaint.measureText(text);得到字的寬度2.小球的寬度/2-字的寬度/2。)
2.畫字時的y坐標(1.Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();得到字體屬性測量類。2.(fontMetrics.ascent + fontMetrics.descent) / 2 得到字的高度。3.小球的高度/2-字體的高度/2)
畫個圖就很好理解了:

/** * 畫小球及文字。如果小球是在拖動狀態就顯示android圖標,如果不是拖動狀態就顯示小球。 * @param canvas */ @Override protected void onDraw(Canvas canvas) { if (isDraging){ canvas.drawBitmap(scaledBitmap,0,0,null); }else { //1.畫圓 canvas.drawCircle(width / 2, heigth / 2, width / 2, circlePaint); //2.畫text float textwidth = textPaint.measureText(text);//文本寬度 float x = width / 2 - textwidth / 2; Paint.FontMetrics fontMetrics = textPaint.getFontMetrics(); float dy = -(fontMetrics.ascent + fontMetrics.descent) / 2; float y = heigth / 2 + dy; canvas.drawText(text, x, y, textPaint); } }獲得手機已用內存及總內存的方法:
public long getAvailMemory(Context context) { // 獲取android當前可用內存大小 ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); am.getMemoryInfo(mi); //mi.availMem; 當前系統的可用內存 //return Formatter.formatFileSize(context, mi.availMem);// 將獲取的內存大小規格化 return mi.availMem/(1024*1024); } public long getTotalMemory(Context context) { String str1 = "/proc/meminfo";// 系統內存信息文件 String str2; String[] arrayOfString; long initial_memory = 0; try { FileReader localFileReader = new FileReader(str1); BufferedReader localBufferedReader = new BufferedReader( localFileReader, 8192); str2 = localBufferedReader.readLine();// 讀取meminfo第一行,系統總內存大小 arrayOfString = str2.split("http://s+"); for (String num : arrayOfString) { Log.i(str2, num + "/t"); } initial_memory = Integer.valueOf(arrayOfString[1]).intValue() * 1024;// 獲得系統總內存,單位是KB,乘以1024轉換為Byte localBufferedReader.close(); } catch (IOException e) { } //return Formatter.formatFileSize(context, initial_memory);// Byte轉換為KB或者MB,內存大小規格化 return initial_memory/(1024*1024); }2.創建WindowManager窗體管理類,管理懸浮小球和底部大窗體。
WindowManager類。用來管理整個懸浮小球和手機底部大窗體的顯示和隱藏。
必須在Manifest文件中增加<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />權限。
通過 WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);獲取窗體管理類;
利用wm.addView(view, params);將view增加到窗體中。
利用wm.remove(view,params);將view從窗體中移除。
利用wm.updateViewLayout(view,params);來更新view.
WindowManager.LayoutParams用來設置view的各種屬性。
1.創建FloatViewManager實例。
//單例模式創建 public static FloatViewManager getInstance(Context context){ if (inStance==null){ synchronized(FloatViewManager.class){ if (inStance==null){ inStance=new FloatViewManager(context); } } } return inStance; }2.展示懸浮小球和展示底部窗體的方法。(展示窗體的方法同展示懸浮小球類似。)
/** * 展示浮窗 */ public void showFloatCircleView(){ //參數設置 if (params==null){ params = new WindowManager.LayoutParams(); //寬高 params.width=circleView.width; params.height=circleView.heigth; //對齊方式 params.gravity= Gravity.TOP|Gravity.LEFT; //偏移量 params.x=0; params.y=0; //類型 params.type=WindowManager.LayoutParams.TYPE_TOAST; //設置該window屬性。 params.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; //像素格式 params.format= PixelFormat.RGBA_8888; } //將小球加入窗體中。 wm.addView(circleView, params); } public void showFloatCircleView(){ ...... } 3.當啟動程序,首先創建懸浮小球,小球可以拖拽,點擊小球,手機底部窗體顯示(FloatMenuView),小球隱藏。所以,對小球(circleView)要對其進行setOnTouchListener和setOnClickListener事件監聽。
分析小球的事件分發; 對于小球:
當ACTION_DOWN時,記錄小球的downX,downY,以及startX,startY,
當ACTION_MOVE時,將circleView是否拖拽狀態置為true,記錄小球的moveX,moveY,計算小球移動的距離(dx,dy),然后根據 wm.updateViewLayout(circleView,params);更新小球位置。最后將最后move的坐標賦值給startX,startY。
當ACTION_UP時,將circleView是否拖拽置為false,記錄抬起時的坐標,upx,根據upx和手機屏幕寬度/2,進行判斷,來覺得最終小球是貼在屏幕左側,還是右側。后面為小球拖拽的誤差。當小球拖拽的距離小于10個像素時,可以觸發小球的點擊事件。(小球的Touch事件,優先于小球的點擊事件,當Touch事件返回true時,此事件被消費,不再向下傳遞事件。當Touch事件返回false時,此事件繼續向下傳遞,從而觸發小球的點擊事件。)
小球的點擊事件:點擊小球,懸浮小球隱藏,手機底部窗體出現。并設置有底部窗體出現時的過渡動畫。
//給circleView設置touch監聽。 private View.OnTouchListener circleViewOnTouchListener=new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //最后按下時的坐標,根據ACTION_MOVE理解。 startX = event.getRawX(); startY = event.getRawY(); //按下時的坐標。 downX = event.getRawX(); downY = event.getRawY(); break; case MotionEvent.ACTION_MOVE: circleView.setDrageState(true); moveX = event.getRawX(); moveY=event.getRawY(); float dx = moveX -startX; float dy=moveY-startY; params.x+=dx; params.y+=dy; wm.updateViewLayout(circleView,params); startX= moveX; startY=moveY; break; case MotionEvent.ACTION_UP: float upx=event.getRawX(); if (upx>getScreenWidth()/2){ params.x=getScreenWidth()-circleView.width; }else { params.x=0; } circleView.setDrageState(false); wm.updateViewLayout(circleView,params); if (Math.abs(moveX-downX)>10){ return true; }else { return false; } default: break; } return false; } };circleView.setOnTouchListener(circleViewOnTouchListener); circleView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Toast.makeText(, "onclick", Toast.LENGTH_SHORT).show(); //
主站蜘蛛池模板:
泾源县|
平陆县|
永登县|
永平县|
通江县|
瑞安市|
方正县|
子长县|
雅江县|
大名县|
大英县|
昌平区|
大庆市|
松江区|
宣威市|
南京市|
娱乐|
普宁市|
饶平县|
合肥市|
临泉县|
瑞安市|
兴国县|
汉川市|
建始县|
珠海市|
潞城市|
梁平县|
庆阳市|
宁城县|
策勒县|
乐清市|
徐州市|
湛江市|
醴陵市|
桐梓县|
久治县|
肃北|
永泰县|
聂拉木县|
大足县|