第四章
1.使用ViewHolder模式提高效率
viewholder模式是提升listview效率的一个很重要的方法,viewholder充分利用了listview的视图缓存机制,避免每次在调用getview的时候都通过findviewbyid()实例化控件,下面是一个通用的viewHolder,利用了泛型,以后不用再adapter内部再定义内部类viewholder了
public class ViewHolder {
/**
*
* @param convertview 所有缓存view的根view
* @param id 唯一表示
* @param <T> 泛型
* @return
*/
public static <T extends View> T getView(View convertview,int id){
SparseArray<View> viewHolder = (SparseArray<View>) convertview.getTag();
//如果根view没有用来缓存View的集合
if (viewHolder == null) {
viewHolder = new SparseArray<View>();
convertview.setTag(viewHolder);//创建集合和根View关联
}
View chidlView = viewHolder.get(id);//获取根View储存在集合中的孩纸
if (chidlView == null) {//如果没有改孩纸
//找到该孩纸
chidlView = convertview.findViewById(id);
viewHolder.put(id, chidlView);//保存到集合
}
return (T) chidlView;
}
}
万能ViewHolder在adapter中使用
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context)
.inflate(R.layout.item, parent, false);
}
TextView name= ViewHolder.get(convertView, R.id.name);
name.setText("sss");
return convertView;
}
2.设置项目间分割线
android:divider=”@color/colorAccent”//分割线颜色
android:dividerHeight=”10dp”//分割线高度
android:divider=”@null”//分割线设置通明不显示
3.隐藏listview滚动条
android:scrollbars=”none”
4.取消listview中item的点击效果
android:listSelector=”#00000000”
或者
android:listSelector=”@android:color/transparent”
5.设置listview显示在第几个
listView.setSelection(position);//类似scrollTo瞬间完成移动的
//平滑移动???????
listView.smoothScrollBy(idstance,duration);
listView.smoothScrollByOffset(offset);
listView.smoothScrollToPosition(position);
6.处理空的listview
listview 提供一个方法,setEmptyView(),通过这个方法可以设置没有数据是的默认样式
7.listview滑动监听
listview的滑动监听是listview中最重要的技巧,很多重写的listview都是在对滑动事件上进行处理,为了更精确的监听,开发者还要通过GestureDetector手势识别,VelocityTracker滑动速度检测等辅助类来完成,这里只介绍两种方法,onTouchListener,onScrollListener来进行监听
onTouchListener
listView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//触摸时操作
break;
case MotionEvent.ACTION_MOVE:
//移动时操作
break;
case MotionEvent.ACTION_UP:
//离开时操作
break;
}
return false;
}
});
onSrollListener
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState){
case SCROLL_STATE_IDLE:
//滑动停止时
break;
case SCROLL_STATE_TOUCH_SCROLL:
//正在滑动
break;
case SCROLL_STATE_FLING:
//手指抛动时
//在离开listview由于惯性继续滑动
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//firstVisibleItem:当前能看见的第一个item
//visibleItemCount 能看见的item总数
//totalItemCount整个listview中item的总数
if(firstVisibleItem + visibleItemCount == totalItemCount){
//滑到最后一行
}
if(firstVisibleItem > lastVisiableItemPosition){
//上滑
}
if(firstVisibleItem < lastVisiableItemPosition){
//下滑
}
//成员变量,用来记录上一次第一个item的位置
lastVisiableItemPosition = firstVisibleItem;
}
});
onScrollStateChanged这个方法在没有手指抛动的状态下只调用2次,否则会调用3次。
onScroll这个方法在listview滑动时会一直调用
listView.getFirstVisiblePosition();//获得可视区域第一个item的id
listView.getLastVisiblePosition();//获得可视区域最后一个item的id
2.ListView 常用扩展
1.具有弹性的listview
在ListView源码中是通过overScrollBy()这个方法来控制滑动到边缘的处理方法的,可以看到这样一个参数maxOverScrollY,这要改变这个值就可以实现带弹性的listview 了,我们将他改变成自己设置的值mMaxOverDistance;
public class FlexibleListView extends ListView {
private static int mMaxOverDistance = 50;
public FlexibleListView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public FlexibleListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public FlexibleListView(Context context) {
super(context);
initView(context);
}
private void initView(Context context) {
//对不同分辨率进行设配
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
float density = metrics.density;
mMaxOverDistance = (int) (density * mMaxOverDistance);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY,
int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY,
boolean isTouchEvent) {
return super.overScrollBy(deltaX, deltaY,
scrollX, scrollY,
scrollRangeX, scrollRangeY,
maxOverScrollX, mMaxOverDistance,
isTouchEvent);
}
}
2.自动显示隐藏布局的listview
获取系统认为的最低滑动距离,当滑动超过这个距离是系统就默认为滑动状态
ViewConfiguration.get(this).getScaledTouchSlop();
public class ScrollHideListView extends Activity {
private Toolbar mToolbar;
private ListView mListView;
private String[] mStr = new String[20];
private int mTouchSlop;
private float mFirstY;
private float mCurrentY;
private int direction;
private ObjectAnimator mAnimator;
private boolean mShow = true;
View.OnTouchListener myTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mFirstY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
mCurrentY = event.getY();
if (mCurrentY - mFirstY > mTouchSlop) {
direction = 0;// down
} else if (mFirstY - mCurrentY > mTouchSlop) {
direction = 1;// up
}
if (direction == 1) {
if (mShow) {
toolbarAnim(1);//hide
mShow = !mShow;
}
} else if (direction == 0) {
if (!mShow) {
toolbarAnim(0);//show
mShow = !mShow;
}
}
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scroll_hide);
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mListView = (ListView) findViewById(R.id.listview);
for (int i = 0; i < mStr.length; i++) {
mStr[i] = "Item " + i;
}
View header = new View(this);
header.setLayoutParams(new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
(int) getResources().getDimension(
R.dimen.abc_action_bar_default_height_material)));
mListView.addHeaderView(header);
mListView.setAdapter(new ArrayAdapter<String>(
ScrollHideListView.this,
android.R.layout.simple_expandable_list_item_1,
mStr));
mListView.setOnTouchListener(myTouchListener);
}
private void toolbarAnim(int flag) {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
if (flag == 0) {
mAnimator = ObjectAnimator.ofFloat(mToolbar,
"translationY", mToolbar.getTranslationY(), 0);
} else {
mAnimator = ObjectAnimator.ofFloat(mToolbar,
"translationY", mToolbar.getTranslationY(),
-mToolbar.getHeight());
}
mAnimator.start();
}
}
3.有多种布局的listview
public class ChatItemListViewAdapter extends BaseAdapter {
private List<ChatItemListViewBean> mData;
private LayoutInflater mInflater;
public ChatItemListViewAdapter(Context context,
List<ChatItemListViewBean> data) {
this.mData = data;
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
//得到布局中类型
@Override
public int getItemViewType(int position) {
ChatItemListViewBean bean = mData.get(position);
return bean.getType();
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
// 根据类型加载不同的布局
if (getItemViewType(position) == 0) {
holder = new ViewHolder();
convertView = mInflater.inflate(
R.layout.chat_item_itemin, null);
holder.icon = (ImageView) convertView.findViewById(
R.id.icon_in);
holder.text = (TextView) convertView.findViewById(
R.id.text_in);
} else {
holder = new ViewHolder();
convertView = mInflater.inflate(
R.layout.chat_item_itemout, null);
holder.icon = (ImageView) convertView.findViewById(
R.id.icon_out);
holder.text = (TextView) convertView.findViewById(
R.id.text_out);
}
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.icon.setImageBitmap(mData.get(position).getIcon());
holder.text.setText(mData.get(position).getText());
return convertView;
}
public final class ViewHolder {
public ImageView icon;
public TextView text;
}
}
4.动态改变listview布局
public class FocusListViewAdapter extends BaseAdapter {
private List<String> mData;
private Context mContext;
private int mCurrentItem = 0;
public FocusListViewAdapter(Context context, List<String> data) {
this.mContext = context;
this.mData = data;
}
public int getCount() {
return mData.size();
}
public Object getItem(int position) {
return mData.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
if (mCurrentItem == position) {
layout.addView(addFocusView(position));
} else {
layout.addView(addNormalView(position));
}
return layout;
}
public void setCurrentItem(int currentItem) {
this.mCurrentItem = currentItem;
}
private View addFocusView(int i) {
ImageView iv = new ImageView(mContext);
iv.setImageResource(R.drawable.ic_launcher);
return iv;
}
private View addNormalView(int i) {
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.HORIZONTAL);
ImageView iv = new ImageView(mContext);
iv.setImageResource(R.drawable.in_icon);
layout.addView(iv, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
TextView tv = new TextView(mContext);
tv.setText(mData.get(i));
layout.addView(tv, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
layout.setGravity(Gravity.CENTER);
return layout;
}
}
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
adapter.setCurrentItem(position);
adapter.notifyDataSetChanged();
}
});
第五章:Android Scroll分析
滑动效果是怎么产生的
1.触控事件MotionEvent:
- ACTION_DOWN:单点触摸按下动作
- ACTION_UP:单点触摸离开动作
- ACTION_MOVE:触控点移动动作
- ACTION_CANCEL:触摸动作取消
- ACTION_OUTSIDE:触摸动作超出边届
- ACTION_POINTER_DOWN:多点触摸按下动作
- ACTION_POINTER_UP:多点离开动作
int x = (int) event.getX();
int y = (int )event.getY();获取当前触控点的视图坐标(x,y)
View提供获取坐标的方法
- getTop():获取到view自身的顶部到其父布局顶部的距离
- getLeft():获取到view自身的左边到其父布局左边的距离
- getRight():获取到view自身的右边到其父布局右边的距离
- getBottom():获取到view自身的底部到其父布局底部的距离
- MotionEvent提供的方法
- getX():点击事件距离控件左边的距离,即视图坐标
- getY():点击事件距离控件顶部的距离,即视图坐标
- getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标
- getRawY():获取点击事件距离整个屏幕顶部的巨鹿,即绝对坐标
实现滑动的七种方法
public class DragView1 extends View {
private int lastX;
private int lastY;
public DragView1(Context context) {
super(context);
ininView();
}
public DragView1(Context context, AttributeSet attrs) {
super(context, attrs);
ininView();
}
public DragView1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView();
}
private void ininView() {
// 给View设置背景颜色,便于观察
setBackgroundColor(Color.BLUE);
}
// 视图坐标方式
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 在当前left、top、right、bottom的基础上加上偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
//同时对left和right进行偏移 offsetLeftAndRight(offsetX);
//同时对top和down进行偏移 offsetTopAndBottom(offsetY);
break;
}
return true;
}
}
public class DragView2 extends View {
private int lastX;
private int lastY;
public DragView2(Context context) {
super(context);
ininView();
}
public DragView2(Context context, AttributeSet attrs) {
super(context, attrs);
ininView();
}
public DragView2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView();
}
private void ininView() {
setBackgroundColor(Color.BLUE);
}
// 绝对坐标方式
@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) (event.getRawX());
int rawY = (int) (event.getRawY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = rawX;
lastY = rawY;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = rawX - lastX;
int offsetY = rawY - lastY;
// 在当前left、top、right、bottom的基础上加上偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
// 重新设置初始坐标
lastX = rawX;
lastY = rawY;
break;
}
return true;
}
}
使用MarginLayoutParams 不用考虑父布局的类型,他们本质上是一样的
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
// LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
}
return true;
}
在view中,系统提供scrollTo,scrollBy两种方式来改变一个view的位置,scrollTo(x,y)表示移动到一个具体的坐标点,而scrollBy(x,y) 表示移动的增量是dx,dy。
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
}
return true;
}
使用Scroller
public class DragView5 extends View {
private int lastX;
private int lastY;
private Scroller mScroller;
public DragView5(Context context) {
super(context);
ininView(context);
}
public DragView5(Context context, AttributeSet attrs) {
super(context, attrs);
ininView(context);
}
public DragView5(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView(context);
}
private void ininView(Context context) {
setBackgroundColor(Color.BLUE);
// 初始化Scroller
mScroller = new Scroller(context);
}
//是否完成整个滑动
@Override
public void computeScroll() {
super.computeScroll();
// 判断Scroller是否执行完毕
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(
mScroller.getCurrX(),
mScroller.getCurrY());
// 通过重绘来不断调用computeScroll
invalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
case MotionEvent.ACTION_UP:
// 手指离开时,执行滑动过程
View viewGroup = ((View) getParent());
//让其回到原来位置
mScroller.startScroll(
viewGroup.getScrollX(),
viewGroup.getScrollY(),
-viewGroup.getScrollX(),
-viewGroup.getScrollY());
invalidate();
break;
}
return true;
}
}
ViewDragHelper
public class DragViewGroup extends FrameLayout {
private ViewDragHelper mViewDragHelper;
private View mMenuView, mMainView;
private int mWidth;
public DragViewGroup(Context context) {
super(context);
initView();
}
public DragViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public DragViewGroup(Context context,
AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = mMenuView.getMeasuredWidth();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//将触摸事件传递给ViewDragHelper,此操作必不可少
mViewDragHelper.processTouchEvent(event);
return true;
}
private void initView() {
//初始化
mViewDragHelper = ViewDragHelper.create(this, callback);
}
private ViewDragHelper.Callback callback =
new ViewDragHelper.Callback() {
// 何时开始检测触摸事件
@Override
public boolean tryCaptureView(View child, int pointerId) {
//如果当前触摸的child是mMainView时开始检测
return mMainView == child;
}
// 触摸到View后回调
@Override
public void onViewCaptured(View capturedChild,
int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
// 当拖拽状态改变,比如idle,dragging
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
// 当位置改变的时候调用,常用与滑动时更改scale等
@Override
public void onViewPositionChanged(View changedView,
int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
// 处理垂直滑动
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
// 处理水平滑动
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
// 拖动结束后调用
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
//手指抬起后缓慢移动到指定位置
if (mMainView.getLeft() < 500) {
//关闭菜单
//相当于Scroller的startScroll方法
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
} else {
//打开菜单
mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}