編輯:Android編程入門
這周作業,要做一個類似QQ的左滑刪除效果的ListView,因為不想給每個item都放一個按鈕,所以決定用PopupWindow,這裡記錄一下
先放一下效果圖:
先說明一下這裡面的問題:
①沒有做到像QQ那樣可以允許item跟隨手指移動,雖然PopupWindow有update方法讓我們動態移動,但是在屏幕外移動會沒有動畫效果,直接彈進來
②仔細觀察可以發現,item的滑動和刪除按鈕的滑動是分開的,無法保證它們會一起播放,QQ的動畫可以
再說說大概的思路,因為我們沒有讓item都帶上Button,所以用到了PopupWindow來做刪除按鈕,我們可以通過自定義ListView來監聽手勢,如果判斷了是左滑動,則讓PopupWindow動態顯示出來,在讓item做一個平移的效果,當兩者的持續時間一致的時候,效果就出來了。接下來,如果用戶點擊了PopupWindow以外的區域,PopupWindow會消失,回調dismiss方法,我們再把item移回原位。
介紹一下PopupWindow,它允許我們在屏幕上的任意位置彈出一個提示框,提示框的布局我們可以自己定義,我們上面的刪除按鈕,其實就是一個PopupWindow。
如果我們要使用PopupWindow,可以像下面這樣,定義一個自定義布局的PopupWindow
View view = LayoutInflater.from(context).inflate(R.layout.item_delete, null); PopupWindow mPopupWindow = new PopupWindow(view, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, true);
如果我們要顯示這個PopupWindow,可以調用showAtLocation或者showAsDropDown,前者是直接把PopupWindow顯示在某個特定位置,後者是在某個位置從上向下彈出,這裡我們調用第一個方法,有興趣的可以嘗試下第二個,參數什麼的可以不用管,不懂的看看API就知道了,之後再調用update方法更新位置信息
mPopupWindow.showAtLocation(itemView, Gravity.LEFT | Gravity.TOP, locations[0]+ itemView.getWidth() - popWidth, locations[1]);
mPopupWindow.update();
至於對PopupWindow設置背景什麼的,需要的時候看看API就知道了!這裡不詳說。
至於效果的實現,我們一步一步的來。
①自定義一個ListView,詳細的做法,已經在下面做了注釋
public class SlidableListView extends ListView { private static final String TAG = "SlidableListView"; private int touchSlop; private View itemView; // 每個Item的View private int mCurrentDownPosition; // 當前點擊的位置,用來找出點擊的Item private PopupWindow mPopupWindow; // 刪除按鈕 private int popHeight, popWidth; // 刪除按鈕的高和寬 private boolean isSliding = false; // 手指是否在向左滑動 private int downX, downY, moveX, moveY; // 點擊的X和Y,以及移動的X和Y private OnItemDeleteListener onItemDeleteListener; // 點擊刪除按鈕時候的回調接口 public SlidableListView(Context context) { super(context); } // 在布局中使用自定義控件調用的構造函數,這裡進行一些必要的初始化 public SlidableListView(Context context, AttributeSet attrs) { super(context, attrs); // 初始化刪除按鈕,記得讓按鈕可以接收焦點 View view = LayoutInflater.from(context).inflate(R.layout.item_delete, null); mPopupWindow = new PopupWindow(view, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, true); // 獲取刪除按鈕的寬和高 view.measure(0, 0); popWidth = view.getMeasuredWidth(); popHeight = view.getMeasuredHeight(); // 獲取滑動的最小距離 touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } public SlidableListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } // 先攔截touch事件,根據不同情況進行分發 @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); int x = (int) ev.getX(), y = (int) ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: downX = x; downY = y; // 如果刪除按鈕正在顯示,那麼不處理這個觸摸事件,並把按鈕隱藏起來 if (mPopupWindow.isShowing()) { mPopupWindow.dismiss(); return false; // 阻止事件傳遞 } mCurrentDownPosition = pointToPosition(downX, downY); // 獲取觸摸的item itemView = getChildAt(mCurrentDownPosition - getFirstVisiblePosition()); break; case MotionEvent.ACTION_MOVE: moveX = x; moveY = y; int deltaX = moveX - downX; int deltaY = moveY - downY; // 當判斷為左滑的時候,把事件交給OnTouchEvent來處理 if (moveX < downX && Math.abs(deltaX) > touchSlop && Math.abs(deltaY) < touchSlop) { isSliding = true; } break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); // 判斷是否已經有左右滑動 if (isSliding) { switch (action) { case MotionEvent.ACTION_UP: isSliding = false; break; case MotionEvent.ACTION_MOVE: // 獲取item在屏幕中的位置信息 int[] locations = new int[2]; itemView.getLocationOnScreen(locations); // 給刪除按鈕設置動畫,這裡自定了出現從右到左平移,隱藏從左到右 mPopupWindow.setAnimationStyle(R.style.PopupWindowAnimations); // 設置刪除按鈕的位置,這裡設置在item的最右端 mPopupWindow.showAtLocation(itemView, Gravity.LEFT | Gravity.TOP, locations[0] + itemView.getWidth() - popWidth, locations[1]); mPopupWindow.update(); // 給item播放平移動畫,配合刪除按鈕 ObjectAnimator.ofFloat(itemView, "translationX", 0, -popWidth).setDuration (200).start(); // 獲取刪除按鈕的實例,這裡是一個TextView TextView delete = (TextView) mPopupWindow.getContentView().findViewById(R.id .delete); // 設置監聽,如果刪除按鈕消失(點擊按鈕外的地方,按下back鍵)回調 mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { @Override public void onDismiss() { // 把item移回原位置 ObjectAnimator.ofFloat(itemView, "translationX", -popWidth, 0) .setDuration(200).start(); } }); // 給刪除按鈕設置點擊事件 delete.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 獲取點擊的item位置,並調用回調接口方法發送出去,隱藏刪除按鈕 int position = SlidableListView.this.getPositionForView(itemView); onItemDeleteListener.onItemDelete(position); mPopupWindow.dismiss(); } }); break; } // 如果有在滑動,消化事件 return true; } return super.onTouchEvent(ev); } public void setOnItemDeleteListener(OnItemDeleteListener onItemDeleteListener) { this.onItemDeleteListener = onItemDeleteListener; } // 回調刪除事件的接口 public interface OnItemDeleteListener { void onItemDelete(int position); } }
②自定義消失和進入的動畫,因為系統中沒有我們想要的平移進入
style.xml:
<style name="PopupWindowAnimations" parent="@android:style/Animation"> <item name="android:windowEnterAnimation">@anim/delete_enter</item> <item name="android:windowExitAnimation">@anim/delete_exit</item> </style>
anim/delete_enter.xml:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromXDelta="200" android:toXDelta="0" android:fromYDelta="0" android:toYDelta="0" android:duration="200"/> </set>
anim/delete_exit.xml:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="0" android:toXDelta="200" android:fromYDelta="0" android:toYDelta="0" android:duration="200"/> </set>
③自定義Adapter,這裡不給出了,都懂
④在布局中使用自定義的ListView
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <sixthweek.fndroid.com.sixthweek.SlidableListView android:id="@+id/slidableListView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
⑤在Activity中使用adapter傳入數據即可
public class SlidableListViewActivity extends AppCompatActivity { private SlidableListView slidableListView; private MyAdapter adapter; private List<Map<String, String>> data; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_slidablelistview); slidableListView = (SlidableListView) findViewById(R.id.slidableListView); data = new ArrayList<>(); for (int i = 0; i < 20; i++) { Map<String, String> map = new HashMap(); map.put("name", "QQ" + i); data.add(map); } adapter = new MyAdapter(this, data); slidableListView.setAdapter(adapter); // 實現回調接口,處理邏輯 slidableListView.setOnItemDeleteListener(new SlidableListView.OnItemDeleteListener() { @Override public void onItemDelete(int position) { data.remove(position); adapter.notifyDataSetChanged(); } }); } }
寫單元測試類1.創建單元測試文件夾,即新建一個用於單元測試的包,存放單元測試的類。2.創建一個類如 ExampleTest,注意要繼承自InstrumentationTe
今天把AndroidStudio升級到1.5後發現所有的個性設置全變為初始化了。包括皮膚啊,字體大小、顏色啊,以及快捷鍵等等。一瞬間就懵了。 升級完後好像有一個彈窗就是
這幾天在回顧Android的基礎知識,就把一些常見的知識點整理一下,以後忘了也可以翻出來看一看。簡單介紹一下Activity的生命周期在API文檔中對生命周期回調的函數描
導讀增強的Doze模式後台優化Data Saver 一.增強的Doze模式Android N對Android M引進的Doze模式進行了進一步的增強,變化體現在