您的位置:首页 > 产品设计 > UI/UE

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)

2014-09-02 07:28 1041 查看


Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)

本文内容:

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

[html] view
plaincopyprint?





<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

[html] view
plaincopyprint?





<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,没有啥实际作用。

[html] view
plaincopyprint?





<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

[html] view
plaincopyprint?





<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

[java] view
plaincopyprint?





<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;

@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>

HorizontalListView.java 这就是自定义的横向listview

[java] view
plaincopyprint?





<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);

}

@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>

HorizontalListViewAdapter.java 横向listview的适配器,我将他单独写到一个java文件里。

[java] view
plaincopyprint?





<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);

}

@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>

下面是效果图:



下图是一个item被选定后,另一个item获得了焦点:



下面是横向时的截图:



要点如下:

1、可以说这个HorizontalListView是完美的,但美中不足的并不是其他人说的不能点击、晃动、加载不全的问题,而是这个横向Listview的高度,如果你设成wrap_cotent那么将会占据整个屏幕,即使你将它适配器里的view的高度限制死,限制成很小,这个HorizontalListView的高度依然是全屏。本文代码里,我把图片缩略图弄成100dip,所以把这个HorizontalListView的高度设为了150dip。
2、在适配器里,我填充了一个图片,下面是文字。为了能让浏览图片时item有反应,搞了一个selector,它的用法详见这里.
但一开始在点击时完全没有反应,参考这里:
/article/2467324.html
为此我的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>
将自然状态下的背景放到了最后,但点击浏览时依然没有作用。其实最根本原因是在布局文件里horizontal_list_item.xml要让这个布局能够clickable,即:android:clickable="true"
3、上一步完成了,还需要点击即select一个item时,让它变色并且保持住,然后点击另外一个item时,让之前得item恢复默认背景。为了实现这个问题,我曾作如下尝试:

[java] view
plaincopyprint?





<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的提供者
。参考这位大大的文章:/article/7511554.html 在适配器里加了一个接口保存选中的索引,然后再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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐