Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
来源:互联网 发布:改变mac host 编辑:程序博客网 时间:2024/04/23 17:02
本文内容:
1、横向ListView的所有实现思路;
2、其中一个最通用的思路HorizontalListView,并基于横向ListView开发一个简单的相册;
3、实现的横向ListView在点击、浏览时item背景会变色,并解决了listview里setSelected造成item的选择状态混乱的问题。
众所周知,ListView默认的方向是垂直的,但有些时候人们更喜欢横向ListView。纵观整个网络,横向ListView的实现思路如下:
1、在布局里用HorizontalScrollView包含一个ListView,参考这里;
2、利用GridView,把它的行数设为1行;
3、有人继承ListView构造了一个HorizontalScrollListView,参见:这里
4、国外一位大牛继承AdapterView<ListAdapter>构造的HorizontalListView,这是以上所有方法里本人认为最正统的方法,本文即基于此方法,参见:这里
下面看源码:
这是Activity的布局文件:activity_main.xml
- <SPAN style="FONT-SIZE: 18px; FONT-FAMILY: 'Comic Sans MS'"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context=".MainActivity"
- >
- <org.yanzi.ui.HorizontalListView
- android:id="@+id/horizon_listview"
- android:layout_width="match_parent"
- android:layout_height="150dip"
- android:layout_alignParentTop="true"
- >
- </org.yanzi.ui.HorizontalListView>
- <ImageView
- android:id="@+id/image_preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/horizon_listview"
- android:layout_centerInParent="true"
- android:clickable="true"
- android:background="@drawable/selector_imageview_background"
- />
- <!-- android:background="@android:drawable/ic_menu_gallery" -->
- </RelativeLayout></SPAN>
<span style="font-family:'Comic Sans MS';font-size:18px;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <org.yanzi.ui.HorizontalListView android:id="@+id/horizon_listview" android:layout_width="match_parent" android:layout_height="150dip" android:layout_alignParentTop="true" > </org.yanzi.ui.HorizontalListView> <ImageView android:id="@+id/image_preview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/horizon_listview" android:layout_centerInParent="true" android:clickable="true" android:background="@drawable/selector_imageview_background" /> <!-- android:background="@android:drawable/ic_menu_gallery" --></RelativeLayout></span>
这是横向listview的每个item的布局,图片+文字,horizontal_list_item.xml
- <SPAN style="FONT-SIZE: 18px; FONT-FAMILY: 'Comic Sans MS'"><?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingLeft="2dip"
- android:paddingRight="2dip"
- android:paddingTop="2dip"
- android:paddingBottom="2dip"
- android:orientation="vertical"
- android:gravity="center"
- android:clickable="true"
- android:background="@drawable/selector_item_background">
- <ImageView
- android:id="@+id/img_list_item"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/text_list_item"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"/>
- </LinearLayout>
- </SPAN>
<span style="font-family:'Comic Sans MS';font-size:18px;"><?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="2dip" android:paddingRight="2dip" android:paddingTop="2dip" android:paddingBottom="2dip" android:orientation="vertical" android:gravity="center" android:clickable="true" android:background="@drawable/selector_item_background"> <ImageView android:id="@+id/img_list_item" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/text_list_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"/> </LinearLayout></span>
下面文件是selector_imageview_background.xml,这是大图片你点击浏览时背景发生变化的selector,没有啥实际作用。
- <SPAN style="FONT-SIZE: 18px; FONT-FAMILY: 'Comic Sans MS'"><?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@android:color/holo_green_light" android:state_pressed="true"/>
- <item android:drawable="@android:color/holo_green_light" android:state_focused="true"/>
- <item android:drawable="@drawable/image_background"></item>
- <!-- android:drawable="@android:color/transparent" -->
- </selector></SPAN>
<span style="font-family:'Comic Sans MS';font-size:18px;"><?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@android:color/holo_green_light" android:state_pressed="true"/> <item android:drawable="@android:color/holo_green_light" android:state_focused="true"/> <item android:drawable="@drawable/image_background"></item><!-- android:drawable="@android:color/transparent" --></selector></span>
下面是每个item的selector,在focus和select时颜色会发生变化:selector_item_background.xml
- <SPAN style="FONT-SIZE: 18px; FONT-FAMILY: 'Comic Sans MS'"><?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@android:color/holo_red_light" android:state_selected="true"/>
- <item android:drawable="@android:color/holo_green_dark" android:state_pressed="true"/>
- <item android:drawable="@android:color/transparent"/>
- </selector></SPAN>
<span style="font-family:'Comic Sans MS';font-size:18px;"><?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@android:color/holo_red_light" android:state_selected="true"/> <item android:drawable="@android:color/holo_green_dark" android:state_pressed="true"/> <item android:drawable="@android:color/transparent"/></selector></span>
主程序:MainActivity.java
- <SPAN style="FONT-SIZE: 18px; FONT-FAMILY: 'Comic Sans MS'">package org.yanzi.testhorizontallistview;
- import org.yanzi.ui.HorizontalListView;
- import org.yanzi.ui.HorizontalListViewAdapter;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.Menu;
- import android.view.View;
- import android.widget.AdapterView;
- import android.widget.AdapterView.OnItemClickListener;
- import android.widget.ImageView;
- public class MainActivity extends Activity {
- HorizontalListView hListView;
- HorizontalListViewAdapter hListViewAdapter;
- ImageView previewImg;
- View olderSelectView = null;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initUI();
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
- public void initUI(){
- hListView = (HorizontalListView)findViewById(R.id.horizon_listview);
- previewImg = (ImageView)findViewById(R.id.image_preview);
- String[] titles = {"怀师", "南怀瑾军校", "闭关", "南怀瑾", "南公庄严照", "怀师法相"};
- final int[] ids = {R.drawable.nanhuaijin_miss, R.drawable.nanhuaijin_school,
- R.drawable.nanhuaijin_biguan, R.drawable.nanhuaijin,
- R.drawable.nanhuaijin_zhuangyan, R.drawable.nanhuaijin_faxiang};
- hListViewAdapter = new HorizontalListViewAdapter(getApplicationContext(),titles,ids);
- hListView.setAdapter(hListViewAdapter);
- // hListView.setOnItemSelectedListener(new OnItemSelectedListener() {
- //
- // @Override
- // public void onItemSelected(AdapterView<?> parent, View view,
- // int position, long id) {
- // // TODO Auto-generated method stub
- // if(olderSelected != null){
- // olderSelected.setSelected(false); //上一个选中的View恢复原背景
- // }
- // olderSelected = view;
- // view.setSelected(true);
- // }
- //
- // @Override
- // public void onNothingSelected(AdapterView<?> parent) {
- // // TODO Auto-generated method stub
- //
- // }
- // });
- hListView.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
- // TODO Auto-generated method stub
- // if(olderSelectView == null){
- // olderSelectView = view;
- // }else{
- // olderSelectView.setSelected(false);
- // olderSelectView = null;
- // }
- // olderSelectView = view;
- // view.setSelected(true);
- previewImg.setImageResource(ids[position]);
- hListViewAdapter.setSelectIndex(position);
- hListViewAdapter.notifyDataSetChanged();
- }
- });
- }
- }
- </SPAN>
<span style="font-family:'Comic Sans MS';font-size:18px;">package org.yanzi.testhorizontallistview;import org.yanzi.ui.HorizontalListView;import org.yanzi.ui.HorizontalListViewAdapter;import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.view.View;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ImageView;public class MainActivity extends Activity {HorizontalListView hListView;HorizontalListViewAdapter hListViewAdapter;ImageView previewImg;View olderSelectView = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initUI();}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}public void initUI(){hListView = (HorizontalListView)findViewById(R.id.horizon_listview);previewImg = (ImageView)findViewById(R.id.image_preview);String[] titles = {"怀师", "南怀瑾军校", "闭关", "南怀瑾", "南公庄严照", "怀师法相"};final int[] ids = {R.drawable.nanhuaijin_miss, R.drawable.nanhuaijin_school,R.drawable.nanhuaijin_biguan, R.drawable.nanhuaijin,R.drawable.nanhuaijin_zhuangyan, R.drawable.nanhuaijin_faxiang};hListViewAdapter = new HorizontalListViewAdapter(getApplicationContext(),titles,ids);hListView.setAdapter(hListViewAdapter);//hListView.setOnItemSelectedListener(new OnItemSelectedListener() {////@Override//public void onItemSelected(AdapterView<?> parent, View view,//int position, long id) {//// TODO Auto-generated method stub//if(olderSelected != null){//olderSelected.setSelected(false); //上一个选中的View恢复原背景//}//olderSelected = view;//view.setSelected(true);//}////@Override//public void onNothingSelected(AdapterView<?> parent) {//// TODO Auto-generated method stub////}//});hListView.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view,int position, long id) {// TODO Auto-generated method stub//if(olderSelectView == null){//olderSelectView = view;//}else{//olderSelectView.setSelected(false);//olderSelectView = null;//}//olderSelectView = view;//view.setSelected(true);previewImg.setImageResource(ids[position]);hListViewAdapter.setSelectIndex(position);hListViewAdapter.notifyDataSetChanged();}});}}</span>
HorizontalListView.java 这就是自定义的横向listview
- <SPAN style="FONT-SIZE: 18px; FONT-FAMILY: 'Comic Sans MS'">package org.yanzi.ui;
- /*
- * HorizontalListView.java v1.5
- *
- *
- * The MIT License
- * Copyright (c) 2011 Paul Soucy (paul@dev-smart.com)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
- import java.util.LinkedList;
- import java.util.Queue;
- import android.content.Context;
- import android.database.DataSetObserver;
- import android.graphics.Rect;
- import android.util.AttributeSet;
- import android.view.GestureDetector;
- import android.view.GestureDetector.OnGestureListener;
- import android.view.MotionEvent;
- import android.view.View;
- import android.widget.AdapterView;
- import android.widget.ListAdapter;
- import android.widget.Scroller;
- public class HorizontalListView extends AdapterView<ListAdapter> {
- public boolean mAlwaysOverrideTouch = true;
- protected ListAdapter mAdapter;
- private int mLeftViewIndex = -1;
- private int mRightViewIndex = 0;
- protected int mCurrentX;
- protected int mNextX;
- private int mMaxX = Integer.MAX_VALUE;
- private int mDisplayOffset = 0;
- protected Scroller mScroller;
- private GestureDetector mGesture;
- private Queue<View> mRemovedViewQueue = new LinkedList<View>();
- private OnItemSelectedListener mOnItemSelected;
- private OnItemClickListener mOnItemClicked;
- private OnItemLongClickListener mOnItemLongClicked;
- private boolean mDataChanged = false;
- public HorizontalListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- initView();
- }
- private synchronized void initView() {
- mLeftViewIndex = -1;
- mRightViewIndex = 0;
- mDisplayOffset = 0;
- mCurrentX = 0;
- mNextX = 0;
- mMaxX = Integer.MAX_VALUE;
- mScroller = new Scroller(getContext());
- mGesture = new GestureDetector(getContext(), mOnGesture);
- }
- @Override
- public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
- mOnItemSelected = listener;
- }
- @Override
- public void setOnItemClickListener(AdapterView.OnItemClickListener listener){
- mOnItemClicked = listener;
- }
- @Override
- public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener) {
- mOnItemLongClicked = listener;
- }
- private DataSetObserver mDataObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- synchronized(HorizontalListView.this){
- mDataChanged = true;
- }
- invalidate();
- requestLayout();
- }
- @Override
- public void onInvalidated() {
- reset();
- invalidate();
- requestLayout();
- }
- };
- @Override
- public ListAdapter getAdapter() {
- return mAdapter;
- }
- @Override
- public View getSelectedView() {
- //TODO: implement
- return null;
- }
- @Override
- public void setAdapter(ListAdapter adapter) {
- if(mAdapter != null) {
- mAdapter.unregisterDataSetObserver(mDataObserver);
- }
- mAdapter = adapter;
- mAdapter.registerDataSetObserver(mDataObserver);
- reset();
- }
- private synchronized void reset(){
- initView();
- removeAllViewsInLayout();
- requestLayout();
- }
- @Override
- public void setSelection(int position) {
- //TODO: implement
- }
- private void addAndMeasureChild(final View child, int viewPos) {
- LayoutParams params = child.getLayoutParams();
- if(params == null) {
- params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- }
- addViewInLayout(child, viewPos, params, true);
- child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
- }
- @Override
- protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if(mAdapter == null){
- return;
- }
- if(mDataChanged){
- int oldCurrentX = mCurrentX;
- initView();
- removeAllViewsInLayout();
- mNextX = oldCurrentX;
- mDataChanged = false;
- }
- if(mScroller.computeScrollOffset()){
- int scrollx = mScroller.getCurrX();
- mNextX = scrollx;
- }
- if(mNextX <= 0){
- mNextX = 0;
- mScroller.forceFinished(true);
- }
- if(mNextX >= mMaxX) {
- mNextX = mMaxX;
- mScroller.forceFinished(true);
- }
- int dx = mCurrentX - mNextX;
- removeNonVisibleItems(dx);
- fillList(dx);
- positionItems(dx);
- mCurrentX = mNextX;
- if(!mScroller.isFinished()){
- post(new Runnable(){
- @Override
- public void run() {
- requestLayout();
- }
- });
- }
- }
- private void fillList(final int dx) {
- int edge = 0;
- View child = getChildAt(getChildCount()-1);
- if(child != null) {
- edge = child.getRight();
- }
- fillListRight(edge, dx);
- edge = 0;
- child = getChildAt(0);
- if(child != null) {
- edge = child.getLeft();
- }
- fillListLeft(edge, dx);
- }
- private void fillListRight(int rightEdge, final int dx) {
- while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) {
- View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);
- addAndMeasureChild(child, -1);
- rightEdge += child.getMeasuredWidth();
- if(mRightViewIndex == mAdapter.getCount()-1) {
- mMaxX = mCurrentX + rightEdge - getWidth();
- }
- if (mMaxX < 0) {
- mMaxX = 0;
- }
- mRightViewIndex++;
- }
- }
- private void fillListLeft(int leftEdge, final int dx) {
- while(leftEdge + dx > 0 && mLeftViewIndex >= 0) {
- View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this);
- addAndMeasureChild(child, 0);
- leftEdge -= child.getMeasuredWidth();
- mLeftViewIndex--;
- mDisplayOffset -= child.getMeasuredWidth();
- }
- }
- private void removeNonVisibleItems(final int dx) {
- View child = getChildAt(0);
- while(child != null && child.getRight() + dx <= 0) {
- mDisplayOffset += child.getMeasuredWidth();
- mRemovedViewQueue.offer(child);
- removeViewInLayout(child);
- mLeftViewIndex++;
- child = getChildAt(0);
- }
- child = getChildAt(getChildCount()-1);
- while(child != null && child.getLeft() + dx >= getWidth()) {
- mRemovedViewQueue.offer(child);
- removeViewInLayout(child);
- mRightViewIndex--;
- child = getChildAt(getChildCount()-1);
- }
- }
- private void positionItems(final int dx) {
- if(getChildCount() > 0){
- mDisplayOffset += dx;
- int left = mDisplayOffset;
- for(int i=0;i<getChildCount();i++){
- View child = getChildAt(i);
- int childWidth = child.getMeasuredWidth();
- child.layout(left, 0, left + childWidth, child.getMeasuredHeight());
- left += childWidth + child.getPaddingRight();
- }
- }
- }
- public synchronized void scrollTo(int x) {
- mScroller.startScroll(mNextX, 0, x - mNextX, 0);
- requestLayout();
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean handled = super.dispatchTouchEvent(ev);
- handled |= mGesture.onTouchEvent(ev);
- return handled;
- }
- protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- synchronized(HorizontalListView.this){
- mScroller.fling(mNextX, 0, (int)-velocityX, 0, 0, mMaxX, 0, 0);
- }
- requestLayout();
- return true;
- }
- protected boolean onDown(MotionEvent e) {
- mScroller.forceFinished(true);
- return true;
- }
- private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onDown(MotionEvent e) {
- return HorizontalListView.this.onDown(e);
- }
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- return HorizontalListView.this.onFling(e1, e2, velocityX, velocityY);
- }
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
- synchronized(HorizontalListView.this){
- mNextX += (int)distanceX;
- }
- requestLayout();
- return true;
- }
- @Override
- public boolean onSingleTapConfirmed(MotionEvent e) {
- for(int i=0;i<getChildCount();i++){
- View child = getChildAt(i);
- if (isEventWithinView(e, child)) {
- if(mOnItemClicked != null){
- mOnItemClicked.onItemClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i ));
- }
- if(mOnItemSelected != null){
- mOnItemSelected.onItemSelected(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i ));
- }
- break;
- }
- }
- return true;
- }
- @Override
- public void onLongPress(MotionEvent e) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (isEventWithinView(e, child)) {
- if (mOnItemLongClicked != null) {
- mOnItemLongClicked.onItemLongClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i));
- }
- break;
- }
- }
- }
- private boolean isEventWithinView(MotionEvent e, View child) {
- Rect viewRect = new Rect();
- int[] childPosition = new int[2];
- child.getLocationOnScreen(childPosition);
- int left = childPosition[0];
- int right = left + child.getWidth();
- int top = childPosition[1];
- int bottom = top + child.getHeight();
- viewRect.set(left, top, right, bottom);
- return viewRect.contains((int) e.getRawX(), (int) e.getRawY());
- }
- };
- }
- </SPAN>
<span style="font-family:'Comic Sans MS';font-size:18px;">package org.yanzi.ui;/* * HorizontalListView.java v1.5 * * * The MIT License * Copyright (c) 2011 Paul Soucy (paul@dev-smart.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */import java.util.LinkedList;import java.util.Queue;import android.content.Context;import android.database.DataSetObserver;import android.graphics.Rect;import android.util.AttributeSet;import android.view.GestureDetector;import android.view.GestureDetector.OnGestureListener;import android.view.MotionEvent;import android.view.View;import android.widget.AdapterView;import android.widget.ListAdapter;import android.widget.Scroller;public class HorizontalListView extends AdapterView<ListAdapter> {public boolean mAlwaysOverrideTouch = true;protected ListAdapter mAdapter;private int mLeftViewIndex = -1;private int mRightViewIndex = 0;protected int mCurrentX;protected int mNextX;private int mMaxX = Integer.MAX_VALUE;private int mDisplayOffset = 0;protected Scroller mScroller;private GestureDetector mGesture;private Queue<View> mRemovedViewQueue = new LinkedList<View>();private OnItemSelectedListener mOnItemSelected;private OnItemClickListener mOnItemClicked;private OnItemLongClickListener mOnItemLongClicked;private boolean mDataChanged = false;public HorizontalListView(Context context, AttributeSet attrs) {super(context, attrs);initView();}private synchronized void initView() {mLeftViewIndex = -1;mRightViewIndex = 0;mDisplayOffset = 0;mCurrentX = 0;mNextX = 0;mMaxX = Integer.MAX_VALUE;mScroller = new Scroller(getContext());mGesture = new GestureDetector(getContext(), mOnGesture);}@Overridepublic void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {mOnItemSelected = listener;}@Overridepublic void setOnItemClickListener(AdapterView.OnItemClickListener listener){mOnItemClicked = listener;}@Overridepublic void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener) {mOnItemLongClicked = listener;}private DataSetObserver mDataObserver = new DataSetObserver() {@Overridepublic void onChanged() {synchronized(HorizontalListView.this){mDataChanged = true;}invalidate();requestLayout();}@Overridepublic void onInvalidated() {reset();invalidate();requestLayout();}};@Overridepublic ListAdapter getAdapter() {return mAdapter;}@Overridepublic View getSelectedView() {//TODO: implementreturn null;}@Overridepublic void setAdapter(ListAdapter adapter) {if(mAdapter != null) {mAdapter.unregisterDataSetObserver(mDataObserver);}mAdapter = adapter;mAdapter.registerDataSetObserver(mDataObserver);reset();}private synchronized void reset(){initView();removeAllViewsInLayout(); requestLayout();}@Overridepublic void setSelection(int position) {//TODO: implement}private void addAndMeasureChild(final View child, int viewPos) {LayoutParams params = child.getLayoutParams();if(params == null) {params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);}addViewInLayout(child, viewPos, params, true);child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));}@Overrideprotected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);if(mAdapter == null){return;}if(mDataChanged){int oldCurrentX = mCurrentX;initView();removeAllViewsInLayout();mNextX = oldCurrentX;mDataChanged = false;}if(mScroller.computeScrollOffset()){int scrollx = mScroller.getCurrX();mNextX = scrollx;}if(mNextX <= 0){mNextX = 0;mScroller.forceFinished(true);}if(mNextX >= mMaxX) {mNextX = mMaxX;mScroller.forceFinished(true);}int dx = mCurrentX - mNextX;removeNonVisibleItems(dx);fillList(dx);positionItems(dx);mCurrentX = mNextX;if(!mScroller.isFinished()){post(new Runnable(){@Overridepublic void run() {requestLayout();}});}}private void fillList(final int dx) {int edge = 0;View child = getChildAt(getChildCount()-1);if(child != null) {edge = child.getRight();}fillListRight(edge, dx);edge = 0;child = getChildAt(0);if(child != null) {edge = child.getLeft();}fillListLeft(edge, dx);}private void fillListRight(int rightEdge, final int dx) {while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) {View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);addAndMeasureChild(child, -1);rightEdge += child.getMeasuredWidth();if(mRightViewIndex == mAdapter.getCount()-1) {mMaxX = mCurrentX + rightEdge - getWidth();}if (mMaxX < 0) {mMaxX = 0;}mRightViewIndex++;}}private void fillListLeft(int leftEdge, final int dx) {while(leftEdge + dx > 0 && mLeftViewIndex >= 0) {View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this);addAndMeasureChild(child, 0);leftEdge -= child.getMeasuredWidth();mLeftViewIndex--;mDisplayOffset -= child.getMeasuredWidth();}}private void removeNonVisibleItems(final int dx) {View child = getChildAt(0);while(child != null && child.getRight() + dx <= 0) {mDisplayOffset += child.getMeasuredWidth();mRemovedViewQueue.offer(child);removeViewInLayout(child);mLeftViewIndex++;child = getChildAt(0);}child = getChildAt(getChildCount()-1);while(child != null && child.getLeft() + dx >= getWidth()) {mRemovedViewQueue.offer(child);removeViewInLayout(child);mRightViewIndex--;child = getChildAt(getChildCount()-1);}}private void positionItems(final int dx) {if(getChildCount() > 0){mDisplayOffset += dx;int left = mDisplayOffset;for(int i=0;i<getChildCount();i++){View child = getChildAt(i);int childWidth = child.getMeasuredWidth();child.layout(left, 0, left + childWidth, child.getMeasuredHeight());left += childWidth + child.getPaddingRight();}}}public synchronized void scrollTo(int x) {mScroller.startScroll(mNextX, 0, x - mNextX, 0);requestLayout();}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {boolean handled = super.dispatchTouchEvent(ev);handled |= mGesture.onTouchEvent(ev);return handled;}protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {synchronized(HorizontalListView.this){mScroller.fling(mNextX, 0, (int)-velocityX, 0, 0, mMaxX, 0, 0);}requestLayout();return true;}protected boolean onDown(MotionEvent e) {mScroller.forceFinished(true);return true;}private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {return HorizontalListView.this.onDown(e);}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {return HorizontalListView.this.onFling(e1, e2, velocityX, velocityY);}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY) {synchronized(HorizontalListView.this){mNextX += (int)distanceX;}requestLayout();return true;}@Overridepublic boolean onSingleTapConfirmed(MotionEvent e) {for(int i=0;i<getChildCount();i++){View child = getChildAt(i);if (isEventWithinView(e, child)) {if(mOnItemClicked != null){mOnItemClicked.onItemClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i ));}if(mOnItemSelected != null){mOnItemSelected.onItemSelected(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i ));}break;}}return true;}@Overridepublic void onLongPress(MotionEvent e) {int childCount = getChildCount();for (int i = 0; i < childCount; i++) {View child = getChildAt(i);if (isEventWithinView(e, child)) {if (mOnItemLongClicked != null) {mOnItemLongClicked.onItemLongClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i));}break;}}}private boolean isEventWithinView(MotionEvent e, View child) { Rect viewRect = new Rect(); int[] childPosition = new int[2]; child.getLocationOnScreen(childPosition); int left = childPosition[0]; int right = left + child.getWidth(); int top = childPosition[1]; int bottom = top + child.getHeight(); viewRect.set(left, top, right, bottom); return viewRect.contains((int) e.getRawX(), (int) e.getRawY()); }};}</span>
HorizontalListViewAdapter.java 横向listview的适配器,我将他单独写到一个java文件里。
- <SPAN style="FONT-SIZE: 18px; FONT-FAMILY: 'Comic Sans MS'">package org.yanzi.ui;
- import org.yanzi.testhorizontallistview.R;
- import org.yanzi.util.BitmapUtil;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.graphics.drawable.Drawable;
- import android.media.ThumbnailUtils;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.TextView;
- public class HorizontalListViewAdapter extends BaseAdapter{
- private int[] mIconIDs;
- private String[] mTitles;
- private Context mContext;
- private LayoutInflater mInflater;
- Bitmap iconBitmap;
- private int selectIndex = -1;
- public HorizontalListViewAdapter(Context context, String[] titles, int[] ids){
- this.mContext = context;
- this.mIconIDs = ids;
- this.mTitles = titles;
- mInflater=(LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//LayoutInflater.from(mContext);
- }
- @Override
- public int getCount() {
- return mIconIDs.length;
- }
- @Override
- public Object getItem(int position) {
- return position;
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder holder;
- if(convertView==null){
- holder = new ViewHolder();
- convertView = mInflater.inflate(R.layout.horizontal_list_item, null);
- holder.mImage=(ImageView)convertView.findViewById(R.id.img_list_item);
- holder.mTitle=(TextView)convertView.findViewById(R.id.text_list_item);
- convertView.setTag(holder);
- }else{
- holder=(ViewHolder)convertView.getTag();
- }
- if(position == selectIndex){
- convertView.setSelected(true);
- }else{
- convertView.setSelected(false);
- }
- holder.mTitle.setText(mTitles[position]);
- iconBitmap = getPropThumnail(mIconIDs[position]);
- holder.mImage.setImageBitmap(iconBitmap);
- return convertView;
- }
- private static class ViewHolder {
- private TextView mTitle ;
- private ImageView mImage;
- }
- private Bitmap getPropThumnail(int id){
- Drawable d = mContext.getResources().getDrawable(id);
- Bitmap b = BitmapUtil.drawableToBitmap(d);
- // Bitmap bb = BitmapUtil.getRoundedCornerBitmap(b, 100);
- int w = mContext.getResources().getDimensionPixelOffset(R.dimen.thumnail_default_width);
- int h = mContext.getResources().getDimensionPixelSize(R.dimen.thumnail_default_height);
- Bitmap thumBitmap = ThumbnailUtils.extractThumbnail(b, w, h);
- return thumBitmap;
- }
- public void setSelectIndex(int i){
- selectIndex = i;
- }
- }</SPAN>
<span style="font-family:'Comic Sans MS';font-size:18px;">package org.yanzi.ui;import org.yanzi.testhorizontallistview.R;import org.yanzi.util.BitmapUtil;import android.content.Context;import android.graphics.Bitmap;import android.graphics.drawable.Drawable;import android.media.ThumbnailUtils;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.TextView;public class HorizontalListViewAdapter extends BaseAdapter{private int[] mIconIDs;private String[] mTitles;private Context mContext;private LayoutInflater mInflater;Bitmap iconBitmap;private int selectIndex = -1;public HorizontalListViewAdapter(Context context, String[] titles, int[] ids){this.mContext = context;this.mIconIDs = ids;this.mTitles = titles;mInflater=(LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//LayoutInflater.from(mContext);}@Overridepublic int getCount() {return mIconIDs.length;}@Overridepublic Object getItem(int position) {return position;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;if(convertView==null){holder = new ViewHolder();convertView = mInflater.inflate(R.layout.horizontal_list_item, null);holder.mImage=(ImageView)convertView.findViewById(R.id.img_list_item);holder.mTitle=(TextView)convertView.findViewById(R.id.text_list_item);convertView.setTag(holder);}else{holder=(ViewHolder)convertView.getTag();}if(position == selectIndex){convertView.setSelected(true);}else{convertView.setSelected(false);}holder.mTitle.setText(mTitles[position]);iconBitmap = getPropThumnail(mIconIDs[position]);holder.mImage.setImageBitmap(iconBitmap);return convertView;}private static class ViewHolder {private TextView mTitle ;private ImageView mImage;}private Bitmap getPropThumnail(int id){Drawable d = mContext.getResources().getDrawable(id);Bitmap b = BitmapUtil.drawableToBitmap(d);//Bitmap bb = BitmapUtil.getRoundedCornerBitmap(b, 100);int w = mContext.getResources().getDimensionPixelOffset(R.dimen.thumnail_default_width);int h = mContext.getResources().getDimensionPixelSize(R.dimen.thumnail_default_height);Bitmap thumBitmap = ThumbnailUtils.extractThumbnail(b, w, h);return thumBitmap;}public void setSelectIndex(int i){selectIndex = i;}}</span>
下面是效果图:
下图是一个item被选定后,另一个item获得了焦点:
下面是横向时的截图:
要点如下:
1、可以说这个HorizontalListView是完美的,但美中不足的并不是其他人说的不能点击、晃动、加载不全的问题,而是这个横向Listview的高度,如果你设成wrap_cotent那么将会占据整个屏幕,即使你将它适配器里的view的高度限制死,限制成很小,这个HorizontalListView的高度依然是全屏。本文代码里,我把图片缩略图弄成100dip,所以把这个HorizontalListView的高度设为了150dip。
2、在适配器里,我填充了一个图片,下面是文字。为了能让浏览图片时item有反应,搞了一个selector,它的用法详见这里. 但一开始在点击时完全没有反应,参考这里:
http://blog.csdn.net/ljz2009y/article/details/18820071 为此我的selector如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/holo_red_light" android:state_selected="true"/>
<item android:drawable="@android:color/holo_green_dark" android:state_pressed="true"/>
<item android:drawable="@android:color/transparent"/>
</selector>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/holo_red_light" android:state_selected="true"/>
<item android:drawable="@android:color/holo_green_dark" android:state_pressed="true"/>
<item android:drawable="@android:color/transparent"/>
</selector>
将自然状态下的背景放到了最后,但点击浏览时依然没有作用。其实最根本原因是在布局文件里horizontal_list_item.xml要让这个布局能够clickable,即:android:clickable="true"
3、上一步完成了,还需要点击即select一个item时,让它变色并且保持住,然后点击另外一个item时,让之前得item恢复默认背景。为了实现这个问题,我曾作如下尝试:
- <SPAN style="FONT-SIZE: 18px; FONT-FAMILY: 'Comic Sans MS'">// if(olderSelectView == null){
- // olderSelectView = view;
- // }else{
- // olderSelectView.setSelected(false);
- // olderSelectView = null;
- // }
- // olderSelectView = view;
- // view.setSelected(true);</SPAN>
<span style="font-family:'Comic Sans MS';font-size:18px;">//if(olderSelectView == null){//olderSelectView = view;//}else{//olderSelectView.setSelected(false);//olderSelectView = null;//}//olderSelectView = view;//view.setSelected(true);</span>
即在click监听里,保存上一个选中的view。遗憾的是这种方法会造成item的选中状态造成混乱,比如第一个item选中了,同时第5个item也莫名其妙的被选中了。上述情况发生在滑动时,即一屏显示不完的情况下。当我横屏时,在所有的item都能一次性显示出来情况下,用上述方法么问题。后来我想到,这可以是适配器里的缓存机制造成的,最好不要再listview适配器外对item作修改,即便修改则一定要调适配器的:hListViewAdapter.notifyDataSetChanged();通知刷新view,毕竟适配器才是view的提供者。参考这位大大的文章:http://longyi-java.iteye.com/blog/976067 在适配器里加了一个接口保存选中的索引,然后再getView函数里进行判断。如果是选中的item,则将布局设为选中状态即可,horizontal_list_item.xml里的Linearlayout就会自动加载那个selector了。而无需像这个参考链接里对每个item的元素分别设置状态。
4、BitmapUtil是个工具类,负责将id转成一个bitmap,然后用android自带的ThumbnailUtils去提取缩略图。
5、之所以horizontal_list_item布局里要设置padding是为了选中item时,整个item有种被圈住的感觉,而不是光下面一点变色。
源码下载:http://download.csdn.net/detail/yanzi1225627/7046295
欢迎Android爱好者加群
Android您问我讲-2,
群号:19241311,备注:yanzi ---------------------------------本文系原创,转载请注明作者:yanzi1225627
源码下载:http://download.csdn.net/detail/yanzi1225627/7046295
源码下载:http://download.csdn.net/detail/yanzi1225627/7046295
转自:http://blog.csdn.net/yanzi1225627/article/details/21294553
0 0
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- 在yii模块中设置默认控制器(yii学习记录)
- 解决mysql汉字存储问题
- 《Netty权威指南》开始预售
- execve的使用方法
- freemarker基本数据类型
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
- Cocos2d-x新建模板编译问题总汇
- 提升jsp应用程序执行效率
- TortoiseSVN 的 下载安装与使用——开发者必备工具之二
- Asp.Net 服务器控件的个人理解
- 网页设计师让你的电子商务网站更有利可图的5大理由
- HDU 1050 (Moving Tables)
- 动物识别产生式系统
- python编程input与raw_input