編輯:關於Android編程
不論什麼領域,在模仿一個東西的時候,我們首先要對它進行需求提取,這樣才能保證做到”惟妙惟肖”。通過對QQ側滑功能的分析,提取出了以下需求:
每個Item都可以側滑,根據Item類型的不同,側滑後顯示的菜單項也不同(聯系人/群組的菜單有:置頂,標為已讀,刪除, 通知類消息展示的菜單只有置頂和刪除);側滑的過程中,如果滑動距離超過第一個菜單的寬度,抬起手指時會顯示全部的菜單,即Item會滑動到最左端;在向右滑動關閉菜單的過程中,如果滑動距離超過最後一個菜單的寬度,抬起手指時會關閉全部菜單, 即Item會恢復至正常展示狀態;如果Item的菜單呈展開狀態,則點擊此Item或按下其他Item,當前的Item的菜單將會關閉;如果沒有Item的菜單呈展開狀態,點擊Item時將進入聊天頁面;觀察Item滑動的過程,發現其是勻速滑動, 而不是快速移動; 不能同時滑動多個Item;通過對需求的分析,首先會想到HorizontalScrollView, 當然,重寫Item的RooView的onTouchListener()也可以實現,但是普通的View只有scrollTo()和scrollBy()方法, 只能快速移動而不能勻速移動,導致滑動的過程很生硬。所以我們使用HorizontalScrollView來實現我們的效果。
根布局其實沒什麼內容,就是一個ListView,這樣就不貼代碼了, 下面我們主要展示一下Item的布局內容:
在使用HorizontalScrollView的時候有以下幾點需要注意:
它和ScrollView一樣,只能有一個子布局,且一般為LinearLayout;只有內容的寬度超過屏幕的寬度HorizontalScrollView才可以滑動; match_parent會失效。默認情況下HorizontalScrollView使用match_parent,如果內容沒有占滿屏幕寬度,則HorizontalScrollView的寬度不會達到屏幕的寬度,要想讓HorizontalScrollView和屏幕寬度一樣,需要添加 android:fillViewport=”true”屬性。同樣其子布局的match_parent也會失效,在這個布局中,id為content_layout的LinearLayout寬度為match_parent, 剛開始我認為它已經占滿了整個屏幕的寬度,然後後面的菜單按鍵就會在屏幕之外,這樣整個布局的跨度超過了屏幕寬度,HorizontalScrollView就可以自動滑動了,結果是我想多了;加載上面的布局,然後設置給ListView設置了布局之後發現HorizontalView根本不能滑動,問題當然很明確,Item布局中match_paren失效,既然布局中不能設置,那我們只能動態設置了,通過以下代碼設置Item中展示內容的布局寬度為屏幕寬度:
// 獲取內容展示布局的布局參數
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.contentLayout.getLayoutParams();
// 設置寬度為屏幕寬度
params.width = getResources().getDisplayMetrics().widthPixels;
// 重設布局參數
holder.contentLayout.setLayoutParams(params);
再次運行發現HorizontalScrollView可以滑動了,看起來效果還不錯,但這只是一個開始。仔細分析一下上面其他的需求,我們發現即便使用HorizontalScrollView, 卻依然需要重寫onTouchListener()才能實現。接下來我們一步一步來實現其他的需求:
需求2和需求3主要是對滑動距離的控制, 那麼只要我們在手指彈起的時候對滑動距離進行判斷,就可以很好地對其進行控制。代碼實現如下:
// 獲取滑動的距離
float distance = Math.abs(event.getX() - downX);
if (distance > 0 && distance < dpToPx(70)) { // 如果滑動距離在70dp內(Button寬度)則回復至原來的位置
v.post(new Runnable() {
@Override
public void run() {
// 如果是向右滑動,則依然恢復至菜單展開狀態
if (isRight) {
((HorizontalScrollView) view).fullScroll(View.FOCUS_RIGHT);
} else {
((HorizontalScrollView)view).fullScroll(View.FOCUS_LEFT);
}
}
});
} else { // 滑動距離超過70dp(Button寬度)則完全展開菜單或關閉菜單
v.post(new Runnable() {
@Override
public void run() {
if (isRight) {
((HorizontalScrollView) view).fullScroll(View.FOCUS_LEFT);
} else {
lastPosition = position;
((HorizontalScrollView)view).fullScroll(View.FOCUS_RIGHT);
}
}
});
}
fullScroll()用來滑動至兩端,View.FOCUS_LEFT表示滑動至最左端,即恢復至正常顯示狀態,View.FOCUS_RIGHT表示滑動至最右段,即展開菜單。但是直接使用fullScroll()是不起作用的,因為fullScroll()的操作是異步的,系統並不會等待fullScroll()執行完成,所以我們需要使用post將其添加到消息隊列中(因為Android是通過消息隊列來實現同步的)同步操作。
有菜單展開的情況,那在某種操作下必然要恢復正常,即需求4的描述。如果點擊已展開的Item,則關閉此Item,如果按下其他的Item,則關閉已展開的Item,通過對需求的理解,我們可以得出需要處理點擊事件和按下事件。具體代碼如下:
// 處理按下其他Item則關閉已展開的Item的情況(lastPosition表示已展開的Item position)
if (lastPosition != -1 && lastPosition != position) {
// 獲取已展開的Item的RootView
View openedItemView = getViewByPosition(listView, lastPosition);
if (openedItemView != null) {
final HorizontalScrollView horizontalScrollView = ((HorizontalScrollView)openedItemView.findViewById(R.id.horizontal_scrollview));
// 將已展開的Item置位
horizontalScrollView.smoothScrollTo(0, 0);
}
}
需要注意的是:smoothScrollTo()是同步操作,直接使用就可以。這裡需要說明一下獲取ListView指定position的ItemView的實現:
private View getViewByPosition(ListView listView, int position) {
// 獲取當前可見的第一個Item的position
int firstItemPos = listView.getFirstVisiblePosition();
// 獲取最後一個可見的Item的position
int lastItemPos = firstItemPos + listView.getChildCount() - 1;
if (position < firstItemPos || position > lastItemPos) {
return listView.getAdapter().getView(position, null, listView);
} else {
int childIndex = position - firstItemPos;
return listView.getChildAt(childIndex);
}
}
剛開始獲取指定position的ItemView的時候使用了listView.getChildAt(childIndex), 結果在滑動到下一頁的時候,點擊Item就出現空指針的情況,看了下getChildAt()函數的源碼,發現其返回的是只當前頁可見的Item, 當然, getChildCount()返回也是當前頁可見的Item的數量。
處理完了第一種情況,接下來我們處理點擊已展開的Item的情況,這個問題其實很明顯,我們只需要在UP事件中處理即可:
// 獲取滑動距離(區分滑動和點擊)
float distance = Math.abs(event.getX() - downX);
if (distance == 0.0) {
//點擊已展開的Item的情況
if (lastPosition == position) {
v.post(new Runnable() {
@Override
public void run() {
((HorizontalScrollView)view).fullScroll(View.FOCUS_LEFT);
lastPosition = -1;
}
});
} else if (lastPosition == -1) {
// 沒有Item展開,點擊時直接響應點擊事件
Toast.makeText(MainActivity.this, "觸發了點擊事件", Toast.LENGTH_SHORT).show();
} else {
// 對按下其他Item導致已展開的Item關閉的情況,對lastPosition進行置位
lastPosition = -1;
}
}
現在可以嘗試我們的滑動刪除了,但是發現竟然支持可以多點觸控,即可以側滑多個Item。So easy! 只要我們關閉listView的多點觸控即可解決此問題。在父布局的ListView中添加如下屬性即可:
android:splitMotionEvents="false"
至此,我們通過HorizontalScrollView實現了QQ側滑刪除的全部需求(通過不同的Item加載不同的菜單很簡單,根據類型對菜單項進行顯示與隱藏即可)。
為了縮小文章的篇幅,在此我們只展示Adapter的getView的代碼:
@Override
public android.view.View getView(final int position, android.view.View convertView, final ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = getLayoutInflater().inflate(R.layout.item_layout, parent, false);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
holder.nameText = (TextView) convertView.findViewById(R.id.name_text);
holder.contentText = (TextView) convertView.findViewById(R.id.content_text);
holder.timeText = (TextView) convertView.findViewById(R.id.time_text);
holder.contentLayout = (LinearLayout) convertView.findViewById(R.id.content_layout);
holder.horizontalScrollView = (HorizontalScrollView) convertView.findViewById(R.id.horizontal_scrollview);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.contentLayout.getLayoutParams();
params.width = getResources().getDisplayMetrics().widthPixels;
holder.contentLayout.setLayoutParams(params);
holder.icon.setImageResource(data.get(position).icon);
holder.nameText.setText(data.get(position).name);
holder.contentText.setText(data.get(position).content);
holder.timeText.setText(data.get(position).time);
holder.horizontalScrollView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
final View view = v;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
if (lastPosition != -1 && lastPosition != position) {
View openedItemView = getViewByPosition(listView, lastPosition);
if (openedItemView != null) {
final HorizontalScrollView horizontalScrollView = ((HorizontalScrollView)openedItemView.findViewById(R.id.horizontal_scrollview));
horizontalScrollView.smoothScrollTo(0, 0);
}
}
break;
case MotionEvent.ACTION_MOVE:
if (event.getX() > lastXOffset) {
isRight = true;
} else {
isRight = false;
}
lastXOffset = event.getX();
break;
case MotionEvent.ACTION_UP:
float distance = Math.abs(event.getX() - downX);
if (distance == 0.0) {
if (lastPosition == position) {
v.post(new Runnable() {
@Override
public void run() {
((HorizontalScrollView)view).fullScroll(View.FOCUS_LEFT);
lastPosition = -1;
}
});
} else if (lastPosition == -1) {
Toast.makeText(MainActivity.this, "觸發了點擊事件", Toast.LENGTH_SHORT).show();
} else {
lastPosition = -1;
}
} else if (distance > 0 && distance < dpToPx(70)) {
v.post(new Runnable() {
@Override
public void run() {
if (isRight) {
((HorizontalScrollView) view).fullScroll(View.FOCUS_RIGHT);
} else {
((HorizontalScrollView)view).fullScroll(View.FOCUS_LEFT);
}
}
});
} else {
v.post(new Runnable() {
@Override
public void run() {
if (isRight) {
((HorizontalScrollView) view).fullScroll(View.FOCUS_LEFT);
} else {
lastPosition = position;
((HorizontalScrollView)view).fullScroll(View.FOCUS_RIGHT);
}
}
});
}
break;
default:
break;
}
return false;
}
});
return convertView;
}
}
ADB WIFI這款插件可以免去我們頻繁的插拔USB數據線的動作,通過綁定手機的無線IP,在AndroidStudio端可以直接運行Module到手機上,可以很方便的進行
本文實例講述了Android使用GPS獲取用戶地理位置並監聽位置變化的方法。分享給大家供大家參考,具體如下:LocationActivity.java/* Locatio
AIDL是什麼? AIDL (Android Interface Definition Language), Android接口定義語言,Android提供的IPC (
基於Android 6.0的源碼剖析, 分析android Service啟動流程中ActivityManagerService所扮演的角色/framewo