編輯:關於Android編程
不得不說,作為一名安卓碼農,總是會有蛋蛋的憂傷,因為CP常說的就是:你看,人家ios的那個效果好炫酷,比如下面這樣的
作為一名合格的碼農,實在不能忍,最後還是實現了這個效果,雖然沒有ios的厲害。。。
實現的思路還是不復雜的,主要分兩個方向:WheelView類似的思想(github一大堆)、ClipToPadding和ClipChildren取巧。因為我是用的取巧,所以我們下面只談第二種方法。
難點有兩個,一是精確地控制listview的item滾動到懸浮框內。因為大多數時候都不會是某個item剛好在懸浮框內的,但是唯一要考慮的情況也只有一種,即懸浮框內同時出現2個或多個item(設計需要,item的高度都是大於等於懸浮框的高度的,所以懸浮框內最多同時出現2個item,小於懸浮框的高度就會出現問題),取出最適合停留的item(這是個相對概念,大家可以修改代碼擴展),我目前需要的就是item中間位置的y值和懸浮視圖中間位置的y值最接近的一項,後面的代碼裡大家會看到如何處理的;
二是當listview只有很少的項時,怎麼讓listview可以滾動呢?這就需要ClipToPadding和ClipChildren來幫忙了。不知道這兩個屬性的童鞋可以上網查一查,我相信你會受益良多。ClipToPadding=false,我對這個屬性簡單理解就是,當你為listview設置了padingTop、padingBottom屬性,listview會有pading的效果,但是當你滑動listview到頂部或底部時,listview的頂部或底部卻不會出現pading的那部分區域,即listview的內容不再被pading的區域遮蓋。ClipChildren=false,就是子視圖可以超出父視圖區域進行繪制。有了這兩個屬性,就可以解決listview item比較少時不能滾動的問題了。
由於設計需要滾動時item視圖的放大和縮小,所以把處理放到了onscrolllistener的onscroll方法裡,最後處理listview精確滾動的代碼則要放到onscrolllistener的onScrollStateChanged方法裡,這些都是可以修改的。
因為要實現這個效果需要adapter配合,所以代碼也比較亂,但是注釋還是比較詳細的,大家慢慢看。
先看看WheelListView
package com.ykbjson.demo.customview.listview;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import com.ykbjson.demo.tools.SLog;
/**
* 包名:com.ykbjson.demo.customview.listview
* 描述:類似WheelView的ListView
* 創建者:yankebin
* 日期:2016/6/1
*/
public class WheelListView extends ListView {
private static final int MAX_Y_OVERSCROLL_DISTANCE = 200;
private final Object SCROLL_LOCK = new Object();
//坐標都是相對於手機屏幕
private int topY;//懸浮框頂部坐標
private int middleY;//懸浮框中間坐標
private int bottomY;//懸浮框底部坐標
private int selectPosition;//滾動時adapter當前選中的position
private boolean fromTouch;//當調用smoothScrollToPositionFromTop()方法時也會觸發onScroll,需要屏蔽掉
private WheelAdapter wheelAdapter;//數據適配器
private OnSelectCallback callback;//滾動時的回調接口
private int mMaxYOverScrollDistance;
public interface OnSelectCallback {
void onHandleScroll(int selectPosition);
void onHandleIdle(WheelListView wheelListView, int selectPosition);
}
public WheelListView(Context context) {
this(context, null);
}
public WheelListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WheelListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// initBounceListView();
setClipChildren(false);
setClipToPadding(false);
}
/**
* 阻尼效果實現
*/
// private void initBounceListView(){
// //get the density of the screen and do some maths with it on the max overscroll distance
// //variable so that you get similar behaviors no matter what the screen size
// final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
// final float density = metrics.density;
// mMaxYOverScrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);
// }
//
// @Override
// protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent){
// //This is where the magic happens, we have replaced the incoming maxOverScrollY with our own custom variable mMaxYOverScrollDistance;
// return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverScrollDistance, isTouchEvent);
// }
public void setAdapter(WheelAdapter adapter) {
super.setAdapter(adapter);
wheelAdapter = adapter;
setCallback(adapter);
}
private void setCallback(OnSelectCallback callback) {
this.callback = callback;
}
/**
* 初始化
*
* @param selectView
* @param rootView
*/
protected void setUp(View selectView, View rootView) {
if (null == selectView || null == rootView) {
return;
}
//讓listview現實的區域剛好和懸浮框重合
setPadding(0, selectView.getTop() , 0, rootView.getBottom() - selectView.getBottom() );
int location1[] = new int[2];
selectView.getLocationOnScreen(location1);
topY = location1[1];
middleY = topY + selectView.getMeasuredHeight() / 2;
bottomY = topY + selectView.getMeasuredHeight();
setUpScroll();
}
/**
* 設置滾動監聽
*/
private void setUpScroll() {
setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (null == callback) {
return;
}
if (scrollState == SCROLL_STATE_IDLE) {
if (!fromTouch) {
return;
}
fromTouch = false;
SLog.d("SCROLL_STATE_IDLE");
//adapter實現了callback接口
callback.onHandleIdle(WheelListView.this,selectPosition);
} else if (scrollState == SCROLL_STATE_TOUCH_SCROLL) {
fromTouch = true;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (null == callback) {
return;
}
if (!fromTouch) {
return;
}
synchronized (SCROLL_LOCK) {
handleScroll(firstVisibleItem);
}
}
});
}
/**
* 處理滾動
*
* @param firstVisibleItem
*/
private void handleScroll(int firstVisibleItem) {
//取出與中線距離最近的item
int tempD = -1;
//本次滑動計算出的position
int tempP = -1;
//遍歷listview當前可見的item,不是所有item
for (int i = 0; i < getChildCount(); i++) {
//計算每個item相對於屏幕的坐標值
View child = getChildAt(i);
int location2[] = new int[2];
child.getLocationOnScreen(location2);
int childBottom = location2[1] + child.getMeasuredHeight();
int childTop = location2[1];
// SLog.d("bottomY : " + bottomY + " topY : " + topY + " middleY : " + middleY + " childBottom : " + childBottom + " childTop : " + childTop);
//在懸浮框區域外的,排除掉
if (childBottom < topY || childTop > bottomY) {
continue;
}
//找到item中線離懸浮框中線最近的item,比距離即可
int childMiddleY = childBottom - child.getMeasuredHeight() / 2;
int position = firstVisibleItem + i;//當前item真正的position
int distance = Math.abs(middleY - childMiddleY);
if (tempD == -1) {
tempD = distance;
tempP = position;
} else if (tempD > distance) {
tempD = distance;
tempP = position;
}
}
if (tempP < 0) {
tempP = 0;
} else if (tempP > wheelAdapter.getCount() - 1) {
tempP = wheelAdapter.getCount() - 1;
}
//防止多次notify同一個position
if (selectPosition == tempP) {
return;
}
selectPosition = tempP;
callback.onHandleScroll(selectPosition);
}
}
再看看WheelAdapter
package com.ykbjson.demo.customview.listview;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.drivingassisstantHouse.library.base.SimpleAdapterHolder;
import java.util.List;
/**
* 包名:com.ykbjson.demo.customview.listview
* 描述:類似WheelView的ListView的adapter
* 創建者:yankebin
* 日期:2016/6/1
*/
public abstract class WheelAdapter extends BaseAdapter implements WheelListView.OnSelectCallback {
/**
* 數據源
*/
private List mData;
/**
* 上下文
*/
private Context mContext;
/**
* item布局索引
*/
private int layoutId;
public Context getmContext() {
return mContext;
}
public void setmContext(Context mContext) {
this.mContext = mContext;
}
public List getmData() {
return mData;
}
public void setmData(List data) {
this.mData = data;
}
public int getLayoutId() {
return layoutId;
}
public void setLayoutId(int layoutId) {
this.layoutId = layoutId;
}
/**
* @param context 上下文
* @param data 數據源
* @param id item的布局資源文件
*/
public WheelAdapter(Context context, List data, int id) {
this.mContext = context;
this.mData = data;
this.layoutId = id;
}
/**
* 數據源改變,刷新界面
*
* @param data
*/
public void refersh(List data) {
this.mData = data;
notifyDataSetChanged();
}
@Override
public int getCount() {
if (mData != null) {
return mData.size();
}
return 0;
}
@Override
public T getItem(int position) {
if (mData != null) {
return mData.get(position);
}
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SimpleAdapterHolder holder = SimpleAdapterHolder.get(convertView, parent, layoutId,
position);
covertView(holder, position, mData, getItem(position));
return holder.getmConvertView();
}
/**
* 子類可重寫此方法實現不同的滾動效果
* @param wheelListView
* @param selectPosition
*/
@Override
public void onHandleIdle(WheelListView wheelListView, int selectPosition) {
notifyDataSetChanged();
//精確滾動到某個item的方法,其他的請看api
wheelListView.smoothScrollToPositionFromTop(selectPosition, 0, 400);
}
public abstract void covertView(SimpleAdapterHolder holder, int position, List dataSource, T data);
}
SimpleAdapterHolder其實就是一般的viewHolder
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* 包名:com.ykbjson.demo.customview.listview
* 描述:通用viewholder
* 創建者:yankebin
* 日期:2016/6/1
*/
public class SimpleAdapterHolder {
@SuppressWarnings("unused")
private int mPosition;
private View mConvertView;
SparseArray mMembers;
public View getmConvertView() {
return mConvertView;
}
public SimpleAdapterHolder() {
}
private SimpleAdapterHolder(ViewGroup parent, int layoutId, int position) {
this.mPosition = position;
this.mMembers = new SparseArray();
mConvertView = LayoutInflater.from(parent.getContext()).inflate(
layoutId, parent, false);
mConvertView.setTag(this);
}
public static SimpleAdapterHolder get(View convertView, ViewGroup parent,
int layoutId, int position) {
if (convertView == null) {
return new SimpleAdapterHolder(parent, layoutId, position);
} else {
return (SimpleAdapterHolder) convertView.getTag();
}
}
@SuppressWarnings("unchecked")
public T getView(int viewId) {
View view = mMembers.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mMembers.put(viewId, view);
}
return (T) view;
}
}
為了使用方便,我在外面包裝了一層WheelView
package com.ykbjson.demo.customview.listview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.ykbjson.demo.R;
import com.ykbjson.demo.tools.SLog;
/**
* 包名:com.ykbjson.demo.customview.otherview
* 描述:滾輪視圖
* 創建者:yankebin
* 日期:2016/6/2
*/
public class WheelView extends FrameLayout {
private WheelListView wheelListView;
private View mSelectView;
public WheelView(Context context) {
this(context, null);
}
public WheelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
/**
* 初始化視圖
*
* @param context
* @param attrs
*/
private void initView(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WheelView);
int floatId = typedArray.getResourceId(R.styleable.WheelView_float_layout, -1);
int bgId = typedArray.getResourceId(R.styleable.WheelView_background_resources, -1);
SLog.d("bgId : " + bgId);
typedArray.recycle();
if (-1 == floatId) {
throw new IllegalArgumentException("resId is invalid");
}
//背景
ImageView imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
try {
imageView.setLayerType(LAYER_TYPE_SOFTWARE, null);
} catch (Exception e) {
e.printStackTrace();
}
if (-1 != bgId) {
imageView.setImageResource(bgId);
}
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-1, -1);
addView(imageView, params);
//listview
wheelListView = new WheelListView(context);
wheelListView.setBackgroundColor(Color.TRANSPARENT);
wheelListView.setClipToPadding(false);
wheelListView.setClipChildren(false);
params = new FrameLayout.LayoutParams(-1, -1);
params.gravity = Gravity.CENTER;
addView(wheelListView, params);
//懸浮視圖
mSelectView = LayoutInflater.from(context).inflate(floatId, this, false);
params = new FrameLayout.LayoutParams(-1, -2);
params.gravity = Gravity.CENTER;
addView(mSelectView, params);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
wheelListView.setUp(mSelectView, this);
}
public void setAdapter(WheelAdapter adapter) {
wheelListView.setAdapter(adapter);
}
public WheelListView getWheelListView() {
return wheelListView;
}
}
定義的屬性
最後的使用
在xml裡
<framelayout android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
</framelayout>
在代碼裡
package com.ykbjson.demo.activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Message;
import android.view.View;
import android.widget.TextView;
import com.drivingassisstantHouse.library.base.BaseActivity;
import com.drivingassisstantHouse.library.base.SimpleAdapterHolder;
import com.nineoldandroids.view.ViewHelper;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.ykbjson.demo.R;
import com.ykbjson.demo.bean.AssistantManager;
import com.ykbjson.demo.customview.listview.WheelAdapter;
import com.ykbjson.demo.customview.listview.WheelView;
import com.ykbjson.demo.customview.otherview.CircleImageView;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
/**
* 包名:com.ykbjson.demo.activity
* 描述:
* 創建者:yankebin
* 日期:2016/5/25
*/
public class TestTravelListActivity extends BaseActivity {
@Bind(R.id.wheel)
WheelView wheel;
private ArrayList assistantManagers = new ArrayList<>();
private TravelListAdapter adapter;
private void dataChange() {
adapter = new TravelListAdapter(this, assistantManagers, R.layout.item_assistant_avatar);
wheel.setAdapter(adapter);
}
@Override
public int bindLayout() {
return R.layout.activity_scroll_list;
}
@Override
public void initParms(Bundle parms) {
}
@Override
public void initView(View view) {
for (int i = 0; i < 2; i++) {
AssistantManager manager = new AssistantManager();
manager.setMobile("15208279347");
manager.setName("客服" + i);
manager.setNickName("簡途客戶" + i);
manager.setAssistantId(i);
manager.setAvatar("drawable://" + R.drawable.customer_service_head);
manager.setPhoto("drawable://" + R.drawable.ad_page);
manager.setDescription("呵呵呵呵 的期望的開啟電腦去");
manager.setType(i % 2 == 0 ? 0 : 1);
manager.setTourismSections("成都-都江堰-青城山");
assistantManagers.add(manager);
}
}
@Override
public void doBusiness(Context mContext) {
baseHandler.sendEmptyMessageDelayed(1, 200);
}
@Override
public void resume() {
}
@Override
public void destroy() {
}
@Override
public void handleMessage(Message msg) {
dataChange();
}
/*主要代碼**/
private class TravelListAdapter extends WheelAdapter {
private int mSelectPosition;
/**
* @param context 上下文
* @param data 數據源
* @param id item的布局資源文件
*/
public TravelListAdapter(Context context, List data, int id) {
super(context, data, id);
}
@Override
public void onHandleScroll(int selectPosition) {
mSelectPosition = selectPosition;
notifyDataSetChanged();
}
@Override
public void covertView(SimpleAdapterHolder holder, int position, List dataSource, AssistantManager manager) {
float scale = 1f;
if (mSelectPosition == position) {
scale = 1.2f;
}
TextView tvName = holder.getView(R.id.tv_name);
tvName.setText(manager.getName());
CircleImageView imageView = holder.getView(R.id.iv_avatar);
ImageLoader.getInstance().displayImage(manager.getAvatar(), imageView);
ViewHelper.setScaleX(holder.getmConvertView().findViewById(R.id.layout_content), scale);
ViewHelper.setScaleY(holder.getmConvertView().findViewById(R.id.layout_content), scale);
}
}
}
ViewPagerIndicator,配合ViewPager使用的指示器,可以是標簽類型Tab指示器(如各種新聞app),也可以是小圓圈或小橫線類型的指示器(如引導頁),
首先給大家展示下效果圖,感覺還不錯,請繼續往下閱讀:下拉刷新: 上劃加載 &nbs
前幾篇文章介紹了Listview,但在實際開發中也經常會用到多層的Listview來展示數據,比如qq中的好友展示,所以這張來了解一下ExpandableListview
一、效果圖二、描述更改Android項目中的語言,這個作用於只用於此APP,不會作用於整個系統三、解決方案(一)布局文件<LinearLayout xmlns:an