我认为问题中建议的触摸位置检测方式可能无效,因为有更多高级方式来获取滚动位置。
实现的主要思路如下:
- 使用onScrollChanged()随时跟踪滚动位置;
- 将选择显示为上面的单独视图
GridView
;
- 跟踪所选项目是否可见(使用此问题);
因此,要获得正确的滚动回调,GridView
需要稍微定制:
public class ScrollAwareGridView extends GridView {
/** Callback interface to report immediate scroll changes */
public interface ImmediateScrollListener {
void onImmediateScrollChanged();
}
/** External listener for */
private ImmediateScrollListener mScrollListener = null;
public ScrollAwareGridView(final Context context) {
super(context);
}
public ScrollAwareGridView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public ScrollAwareGridView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (null != mScrollListener) {
mScrollListener.onImmediateScrollChanged();
}
}
/**
* @param listener {@link ImmediateScrollListener}
*/
public void setImmediateScrollListener(final ImmediateScrollListener listener) {
mScrollListener = listener;
}
}
它将通过以下方式放置在 xml 中(main.xml):
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.TestApp.ScrollAwareGridView
android:id="@+id/grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="3" />
<!-- Selection view -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/selectedImage"
android:visibility="gone" />
</RelativeLayout>
在上面的 xml 中,还显示了选择视图。
Activity 将处理项目的选择(但是,最好将选择和滚动跟踪逻辑保留在单独的对象(网格适配器或特定网格片段)中,以免在 Activity 代码中保留特定于网格的逻辑):
public class MyActivity extends Activity implements ScrollAwareGridView.ImmediateScrollListener, AdapterView.OnItemClickListener {
private static final String TAG = "MyActivity";
/** To start / pause music */
private ImageView mSelectedImage = null;
/** position of selected item in the adapter */
private int mSelectedPosition;
/** Main grid view */
private ScrollAwareGridView mGrid;
/** Adapter for grid view */
private ImageAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Show the layout with the test view
setContentView(R.layout.main);
mSelectedImage = (ImageView) findViewById(R.id.selectedImage);
mGrid = (ScrollAwareGridView) findViewById(R.id.grid);
if (null != mGrid) {
mAdapter = new ImageAdapter(this);
mGrid.setAdapter(mAdapter);
mGrid.setImmediateScrollListener(this);
mGrid.setOnItemClickListener(this);
}
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
mSelectedImage.setImageBitmap(null);
mSelectedImage.setVisibility(View.GONE);
mSelectedPosition = -1;
}
@Override
public void onImmediateScrollChanged() {
if (mSelectedPosition >= 0) {
int firstPosition = mGrid.getFirstVisiblePosition(); // This is the same as child #0
int wantedChild = mSelectedPosition - firstPosition;
// Say, first visible position is 8, you want position 10, wantedChild will now be 2
// So that means your view is child #2 in the ViewGroup:
if (wantedChild < 0 || wantedChild >= mGrid.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
mSelectedImage.setVisibility(View.INVISIBLE);
return;
} else {
mSelectedImage.setVisibility(View.VISIBLE);
}
// Could also check if wantedPosition is between listView.getFirstVisiblePosition() and listView.getLastVisiblePosition() instead.
final View selectedView = mGrid.getChildAt(wantedChild);
if (null != selectedView && mSelectedImage.getVisibility() == View.VISIBLE) {
// Put selected view on new position
final ViewGroup.MarginLayoutParams zoomedImageLayoutParams = (ViewGroup.MarginLayoutParams) mSelectedImage.getLayoutParams();
// 200 is difference between zoomed and not zoomed images dimensions
// TODO: Avoid hardcoded values and use resources
final Integer thumbnailX = mGrid.getLeft() + selectedView.getLeft() - (ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION - ImageAdapter.GRID_ITEM_DIMENSION) / 2;
final Integer thumbnailY = mGrid.getTop() + selectedView.getTop() - (ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION - ImageAdapter.GRID_ITEM_DIMENSION) / 2;
zoomedImageLayoutParams.setMargins(thumbnailX,
thumbnailY,
0,
0);
mSelectedImage.setLayoutParams(zoomedImageLayoutParams);
}
}
}
@Override
public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) {
mSelectedPosition = position;
final Bitmap bm = mAdapter.getImage(position);
// Obtain image from adapter, with check if image presented
if (bm != null) {
final ViewGroup.MarginLayoutParams zoomedImageLayoutParams = (ViewGroup.MarginLayoutParams) mSelectedImage.getLayoutParams();
// 200 is difference between zoomed and not zoomed images dimensions
// TODO: Avoid hardcoded values and use resources
final Integer thumbnailX = mGrid.getLeft() + view.getLeft() - (ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION - ImageAdapter.GRID_ITEM_DIMENSION) / 2;
final Integer thumbnailY = mGrid.getTop() + view.getTop() - (ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION - ImageAdapter.GRID_ITEM_DIMENSION) / 2;
zoomedImageLayoutParams.setMargins(thumbnailX,
thumbnailY,
0,
0);
zoomedImageLayoutParams.height = ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION;
zoomedImageLayoutParams.width = ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION;
mSelectedImage.setImageBitmap(bm);
mSelectedImage.setScaleType(ImageView.ScaleType.CENTER);
mSelectedImage.setLayoutParams(zoomedImageLayoutParams);
mSelectedImage.setVisibility(View.VISIBLE);
}
}
}
下面是GridView
s适配器。但是,其中没有任何与滚动跟踪相关的具体内容(我从这个答案中重复使用的大部分代码):
public class ImageAdapter extends BaseAdapter {
private static final String TAG = "ImageAdapter";
/** For creation of child ImageViews */
private Context mContext;
public static final Integer[] IMAGES_RESOURCES = {
R.drawable.image001, R.drawable.image002, R.drawable.image003, R.drawable.image004,
R.drawable.image005, R.drawable.image006, R.drawable.image007, R.drawable.image008,
R.drawable.image009, R.drawable.image010, R.drawable.image011, R.drawable.image012,
R.drawable.image013, R.drawable.image014, R.drawable.image015, R.drawable.image016,
R.drawable.image017, R.drawable.image018, R.drawable.image019, R.drawable.image020,
R.drawable.image021, R.drawable.image022, R.drawable.image023, R.drawable.image024,
R.drawable.image025, R.drawable.image026, R.drawable.image027, R.drawable.image028,
R.drawable.image029, R.drawable.image030, R.drawable.image031, R.drawable.image032,
R.drawable.image033, R.drawable.image034, R.drawable.image035, R.drawable.image036,
R.drawable.image037, R.drawable.image038, R.drawable.image039, R.drawable.image040,
R.drawable.image041, R.drawable.image042, R.drawable.image043, R.drawable.image044,
R.drawable.image045, R.drawable.image046, R.drawable.image047, R.drawable.image048,
R.drawable.image049, R.drawable.image050
};
// TODO: use resources for that sizes, otherwise You'll GET PROBLEMS on other displays!
public final static int GRID_ITEM_DIMENSION = 300;
public final static int HIGHLIGHTED_GRID_ITEM_DIMENSION = 500;
private Bitmap mHolder = null;
private static final int CACHE_SIZE = 50 * 1024 * 1024; // 8 MiB cache
/** Cache to store all decoded images */
private LruCache<Integer, Bitmap> mBitmapsCache = new LruCache<Integer, Bitmap>(CACHE_SIZE) {
@Override
protected int sizeOf(final Integer key, final Bitmap value) {
return value.getByteCount();
}
@Override
protected void entryRemoved(final boolean evicted, final Integer key, final Bitmap oldValue, final Bitmap newValue) {
if (!oldValue.equals(mHolder)) {
oldValue.recycle();
}
}
};
// Constructor
public ImageAdapter(Context c){
mContext = c;
mHolder = BitmapFactory.decodeResource(c.getResources(), R.drawable.ic_launcher, null);
}
@Override
public int getCount() {
return IMAGES_RESOURCES.length;
}
@Override
public Object getItem(int position) {
return IMAGES_RESOURCES[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(GRID_ITEM_DIMENSION, GRID_ITEM_DIMENSION));
} else {
imageView = (ImageView) convertView;
}
final Bitmap itemBitmap = mBitmapsCache.get(IMAGES_RESOURCES[position]);
if (itemBitmap == null || itemBitmap.isRecycled()) {
Log.e(TAG, position + " is missed, launch decode for " + IMAGES_RESOURCES[position]);
imageView.setImageBitmap(mHolder);
mBitmapsCache.put(IMAGES_RESOURCES[position], mHolder);
new BitmapWorkerTask(mBitmapsCache, mContext.getResources(), this).execute(IMAGES_RESOURCES[position]);
} else {
Log.e(TAG, position + " is here for " + IMAGES_RESOURCES[position]);
imageView.setImageBitmap(itemBitmap);
}
return imageView;
}
/**
* Obtains image at position (if there's only holder, then null to be returned)
*
* @param position int position in the adapter
*
* @return {@link Bitmap} image at position or null if image is not loaded yet
*/
public Bitmap getImage(final int position) {
final Bitmap bm = mBitmapsCache.get(IMAGES_RESOURCES[position]);
return ((mHolder.equals(bm) || bm == null) ? null : bm.copy(Bitmap.Config.ARGB_8888, false));
}
/** AsyncTask for decoding images from resources */
static class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private int data = 0;
private final LruCache<Integer, Bitmap> mCache;
private final Resources mRes;
private final BaseAdapter mAdapter;
public BitmapWorkerTask(LruCache<Integer, Bitmap> cache, Resources res, BaseAdapter adapter) {
// nothing to do here
mCache = cache;
mRes = res;
mAdapter = adapter;
}
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
// Use sizes for selected bitmaps for good up-scaling
return decodeSampledBitmapFromResource(mRes, data, GRID_ITEM_DIMENSION, GRID_ITEM_DIMENSION);
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
mCache.put(data, bitmap);
mAdapter.notifyDataSetChanged();
}
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.outHeight = GRID_ITEM_DIMENSION;
options.outWidth = GRID_ITEM_DIMENSION;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
}