Android 将上一个/下一个按钮添加到;细节片段“;(关于主/细节活动)
我想在主视图/详细视图中添加“上一个/下一个”功能。主程序加载一个GridView(带有缩略图),单击它时,它通过URI(基本上包含相应的图像ID)启动DetailActivity 我想要的是细节活动/片段能够直接跳转到上一个/下一个图像,而无需在主视图中来回切换。理想情况下,我希望捕捉滑动手势 我读到最好的方法是使用ViewPager,使用FragmentStatePagerAdapter保存子视图。但是,我想了解为什么我的第一次尝试不起作用(如下)。(此外,我希望避免对代码进行重大修改,除非我必须这样做。)虽然我的尝试起作用,但我在几次滑动后就收到了OutOfMemory错误/崩溃:Android 将上一个/下一个按钮添加到;细节片段“;(关于主/细节活动),android,gridview,android-cursorloader,Android,Gridview,Android Cursorloader,我想在主视图/详细视图中添加“上一个/下一个”功能。主程序加载一个GridView(带有缩略图),单击它时,它通过URI(基本上包含相应的图像ID)启动DetailActivity 我想要的是细节活动/片段能够直接跳转到上一个/下一个图像,而无需在主视图中来回切换。理想情况下,我希望捕捉滑动手势 我读到最好的方法是使用ViewPager,使用FragmentStatePagerAdapter保存子视图。但是,我想了解为什么我的第一次尝试不起作用(如下)。(此外,我希望避免对代码进行重大修改,除非
10-13 13:11:21.441 4149-4149/com.example.android.galleri.app E/art﹕ Throwing OutOfMemoryError "Failed to allocate a 33177612 byte allocation with 12795712 free bytes and 12MB until OOM"
10-13 13:11:21.455 4149-4149/com.example.android.galleri.app I/art﹕ Clamp target GC heap from 195MB to 192MB
10-13 13:11:21.456 4149-4149/com.example.android.galleri.app I/art﹕ Alloc partial concurrent mark sweep GC freed 6(192B) AllocSpace objects, 0(0B) LOS objects, 6% free, 179MB/192MB, paused 903us total 10.248ms
10-13 13:11:21.474 4149-4149/com.example.android.galleri.app I/art﹕ Clamp target GC heap from 195MB to 192MB
10-13 13:11:21.474 4149-4149/com.example.android.galleri.app I/art﹕ Alloc concurrent mark sweep GC freed 3(96B) AllocSpace objects, 0(0B) LOS objects, 6% free, 179MB/192MB, paused 975us total 18.144ms
10-13 13:11:21.474 4149-4149/com.example.android.galleri.app I/art﹕ Forcing collection of SoftReferences for 31MB allocation
10-13 13:11:21.490 4149-4149/com.example.android.galleri.app I/art﹕ Clamp target GC heap from 195MB to 192MB
10-13 13:11:21.490 4149-4149/com.example.android.galleri.app I/art﹕ Alloc concurrent mark sweep GC freed 3(96B) AllocSpace objects, 0(0B) LOS objects, 6% free, 179MB/192MB, paused 1.060ms total 15.295ms
10-13 13:11:21.490 4149-4149/com.example.android.galleri.app E/art﹕ Throwing OutOfMemoryError "Failed to allocate a 33177612 byte allocation with 12795712 free bytes and 12MB until OOM"
10-13 13:11:21.490 4149-4149/com.example.android.galleri.app D/skia﹕ --- decoder->decode returned false
10-13 13:11:21.491 4149-4149/com.example.android.galleri.app D/AndroidRuntime﹕ Shutting down VM
--------- beginning of crash
10-13 13:11:21.492 4149-4149/com.example.android.galleri.app E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.example.android.galleri.app, PID: 4149
java.lang.OutOfMemoryError: Failed to allocate a 33177612 byte allocation with 12795712 free bytes and 12MB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
at android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:635)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:611)
at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:391)
at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:417)
at com.example.android.galleri.app.DetailFragment.onLoadFinished(DetailFragment.java:302)
at com.example.android.galleri.app.DetailFragment.onLoadFinished(DetailFragment.java:43)
at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:427)
at android.support.v4.app.LoaderManagerImpl$LoaderInfo.onLoadComplete(LoaderManager.java:395)
at android.support.v4.content.Loader.deliverResult(Loader.java:104)
at android.support.v4.content.CursorLoader.deliverResult(CursorLoader.java:73)
at android.support.v4.content.CursorLoader.deliverResult(CursorLoader.java:35)
at android.support.v4.content.AsyncTaskLoader.dispatchOnLoadComplete(AsyncTaskLoader.java:223)
at android.support.v4.content.AsyncTaskLoader$LoadTask.onPostExecute(AsyncTaskLoader.java:61)
at android.support.v4.content.ModernAsyncTask.finish(ModernAsyncTask.java:461)
at android.support.v4.content.ModernAsyncTask.access$500(ModernAsyncTask.java:47)
at android.support.v4.content.ModernAsyncTask$InternalHandler.handleMessage(ModernAsyncTask.java:474)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:211)
at android.app.ActivityThread.main(ActivityThread.java:5389)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1020)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:815)
这是相关的(我希望是)代码。
- 注意
调用来自MainActivity,它将图像正常加载到“DetailFragment”中public-void-onItemSelected(Uri-contentUri)
- 此方法(通过回调/接口)从PhotoFragment调用:GridView的
处理程序,在onClick
方法中创建。这是图像从主到细节的正常加载,工作正常onCreate
- 然后另一种方式:DetailActivity捕获滑动手势,并调用DetailFragment中的
方法(通过成员对象/引用)changeImage
- DetailFragment的
方法通过实用程序类(容器对象)获取PhotoFragment中使用的光标。然后,它获取下一行/上一行并用该ID构造一个新的URI,然后用该URI启动(重新启动?)DetailActivitychangeImage
// OOM crash always occurs HERE (after ~5 swipes)
thumbBitmap = BitmapFactory.decodeFile(thumbData);
MainActivity.java
PhotoFragment.java
DetailFragment.java
public类DetailFragment扩展片段实现LoaderManager.LoaderCallbacks{
私有静态最终字符串PHOTOFRAGMENT_TAG=“PFTAG”;
静态最终字符串详细信息\u URI=“URI”;
私有静态最终整数细节加载程序=0;
私有图像视图mPhotoView、mCloseIcon;
私有文本视图mImageSizeView、mImageDateView;
公共字符串mMemTitle,mMemDesc;
//用于共享的数据
私人长图像;
私有ShareActionProvider mShareActionProvider;
私有静态最终字符串[]详细信息\列={
PhotoContract.PhotoEntry.COLUMN\u IMAGE\u ID,
PhotoContract.PhotoEntry.COLUMN\u DISPLAY\u NAME,
PhotoContract.PhotoEntry.COLUMN_数据,
PhotoContract.PhotoEntry.COLUMN_DESC,
PhotoContract.PhotoEntry.COLUMN\u日期,
添加了PhotoContract.PhotoEntry.COLUMN\u DATE\u,
PhotoContract.PhotoEntry.COLUMN_标题,
PhotoContract.PhotoEntry.COLUMN\u尺寸,
光合同.光条目.柱方向,
PhotoContract.PhotoEntry.COLUMN\u高度,
PhotoContract.PhotoEntry.COLUMN\u宽度
};
静态最终int COL_IMAGE_ID=0;
静态最终整数列显示名称=1;
静态最终整数列数据=2;
静态最终整数列描述=3;
静态最终整数列日期=4;
静态最终整数列添加日期=5;
静态最终整数列标题=6;
静态最终整数列大小=7;
静态最终内柱方向=8;
静态最终内柱高度=9;
静态最终内柱宽度=10;
静态最终int COL_THUMB_ID=0;
静态最终整数COL_THUMB_DATA=1;
静态最终int COL_THUMB_IMAGE_ID=2;
私有Uri mUri;
私有布尔noMedia=false;
私有布尔值mTwoPane=false;
公共详细信息片段(){
SetHasOptions菜单(true);//但仅共享按钮
}
创建选项菜单(菜单菜单,菜单充气机){
充气机。充气(右菜单。细节片段,菜单);
MenuItem MenuItem=menu.findItem(R.id.action\u share);
mShareActionProvider=(ShareActionProvider)MenuItemCompat.getActionProvider(menuItem);
if(mFilePath!=null){
mShareActionProvider.setShareContent(createShareForecastingent());
}
}
公共图像视图getPhotoView(){
返回mPhotoView;
}
@凌驾
CreateView上的公共视图(布局、充气机、视图组容器、捆绑包保存状态){
Bundle arguments=getArguments();
if(参数!=null){
mUri=arguments.getParcelable(DetailFragment.DETAIL\u URI);
}
//从URI获取ID
如果(mUri!=null){
mImageID=PhotoContract.PhotoEntry.getImageIdFromUri(mUri);
}
视图根视图=充气机。充气(R.layout.fragment\u详细信息,容器,错误);
mPhotoView=(ImageView)rootView.findviewbyd(R.id.pic_frame);
mImageSizeView=(TextView)rootView.findviewbyd(R.id.imgSize\u TextView);
mImageDateView=(TextView)rootView.findViewById(R.id.date\u TextView);
返回rootView;
}
//用户滑动;转到上一个(方向=-1)或下一个(方向=1)图像:
公共void changeImage(int方向){
Cursor=Utility.getCursor();
长oldId=mImageID;
if(游标==null)
回来
布尔结果;
开关(方向){
案例1:
if(cursor.isFirst())返回;
结果=cursor.moveToPrevious();
打破
案例1:
if(cursor.isLast())返回;
结果=cursor.moveToNext();
打破
违约:
回来
}
Log.v(“gallery”、“DetailFragment.changeImage”(“+directio
public class MainActivity extends ActionBarActivity implements PhotoFragment.Callback {
private static final String DETAILFRAGMENT_TAG = "DFTAG";
private boolean mTwoPane;
private PhotoFragment mFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (findViewById(R.id.photo_detail_container) != null) {
mTwoPane = true;
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.photo_detail_container, new DetailFragment(), DETAILFRAGMENT_TAG)
.commit();
}
} else {
mTwoPane = false;
}
mFragment = ((PhotoFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_photo));
mFragment.setTwoPaneUI(mTwoPane);
}
@Override
// this method is called from PhotoFragment (GridView) via a callback interface
public void onItemSelected(Uri contentUri) {
if (mTwoPane) {
Bundle args = new Bundle();
args.putParcelable(DetailFragment.DETAIL_URI, contentUri);
DetailFragment fragment = new DetailFragment();
fragment.setArguments(args);
fragment.setTwoPane(true);
getSupportFragmentManager().beginTransaction()
.replace(R.id.photo_detail_container, fragment, DETAILFRAGMENT_TAG)
.commit();
} else {
// launch DetailActivity
Intent intent = new Intent(this, DetailActivity.class)
.setData(contentUri);
startActivity(intent);
}
}
}
public class PhotoFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private final static int LOADER_ID = 87;
private PhotoAdapter mPhotoAdapter;
private int mPosition;
private GridView mGridView;
private TextView mEmptyView;
private int count_mem = -1;
private static final String SELECTED_KEY = "POSITION";
private boolean mTwoPane;
private boolean DF_hidden = false;
// these are the data we want from MediaStore
private final static String[] THUMBNAIL_COLUMNS = {
//PhotoContract.ThumbEntry.COLUMN_THUMB_ID,
PhotoContract.ThumbEntry.COLUMN_THUMB_DATA,
PhotoContract.ThumbEntry.COLUMN_IMAGE_PK,
PhotoContract.ThumbEntry.COLUMN_TITLE,
PhotoContract.ThumbEntry.COLUMN_DESC,
PhotoContract.ThumbEntry.COLUMN_DATE,
PhotoContract.ThumbEntry.COLUMN_FILENAME,
PhotoContract.ThumbEntry.COLUMN_DATA,
PhotoContract.ThumbEntry.COLUMN_IMAGESEQUENCE
};
static final int COL_THUMB_DATA = 0;
static final int COL_IMAGE_ID = 1;
static final int COL_TITLE = 2;
static final int COL_DESC = 3;
static final int COL_DATE = 4;
static final int COL_FILENAME = 5;
static final int COL_DATA = 6;
static final int COL_IMAGESEQUENCE = 7;
public void setTwoPaneUI(boolean pTwoPane) {
mTwoPane = pTwoPane;
}
// interfaces
public interface Callback {
public void onItemSelected(Uri photoUri);
}
public interface FragmentCallback {
public void onTaskDone(int count);
}
public PhotoFragment() {
}
public int getPosition() {
return mPosition;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
getLoaderManager().initLoader(LOADER_ID, null, this);
super.onActivityCreated(savedInstanceState);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onDestroy() {
super.onDestroy();
}
public void restartLoader() {
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mPosition != GridView.INVALID_POSITION) {
outState.putInt(SELECTED_KEY, mPosition);
}
super.onSaveInstanceState(outState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
mPhotoAdapter = new PhotoAdapter(getActivity(), null, 0);
GridView gridView = (GridView) rootView.findViewById(R.id.listview_photo);
gridView.setAdapter(mPhotoAdapter);
mEmptyView = (TextView) rootView.findViewById(R.id.list_empty);
mGridView = gridView;
// handle user clicking on an image
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
if (cursor != null) {
Uri baseUri = PhotoContract.PhotoEntry.buildPhotoUriWithId(cursor.getLong(COL_IMAGE_ID));
Log.v("gallery", "PhotoFragment.onItemClick() image_id = " + cursor.getLong(COL_IMAGE_ID)
+ ", baseUri = " + baseUri.toString());
// make sure detail fragment is visible
if (mTwoPane) showDetailFragment();
else Utility.setPosition(position); // else (single-pane): store position clicked!
((Callback)getActivity()).onItemSelected(baseUri);
}
mPosition = position;
}
});
if (savedInstanceState != null && savedInstanceState.containsKey(SELECTED_KEY)) {
mPosition = savedInstanceState.getInt(SELECTED_KEY);
}
return rootView;
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri thumbs_uri = PhotoContract.ThumbEntry.CONTENT_URI;
return new CursorLoader(getActivity(), thumbs_uri, THUMBNAIL_COLUMNS,
null,null, // read everything (all thumbnails)
null
);
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
if (cursor != null) {
// register observer onto cursor
cursor.setNotificationUri(getActivity().getContentResolver(), PhotoContract.ThumbEntry.CONTENT_URI);
mPhotoAdapter.swapCursor(cursor);
if (mPosition != GridView.INVALID_POSITION) {
mGridView.setSelection(mPosition); // scroll into view
}
}
// save (set) cursor in Utility
Utility.setCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
mPhotoAdapter.swapCursor(null);
}
}
public class DetailActivity extends ActionBarActivity {
private static final String DETAILFRAGMENT_TAG = "DFTAG";
private float x1,x2, y1,y2;
static final int MIN_DISTANCE = 150;
private DetailFragment mFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
if (savedInstanceState == null) {
Bundle arguments = new Bundle();
arguments.putParcelable(DetailFragment.DETAIL_URI, getIntent().getData());
//DetailFragment fragment = new DetailFragment();
mFragment = new DetailFragment();
mFragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.add(R.id.photo_detail_container, mFragment, DETAILFRAGMENT_TAG)
.commit();
}
}
// http://stackoverflow.com/questions/6645537/how-to-detect-the-swipe-left-or-right-in-android
@Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
y1 = event.getY();
break;
case MotionEvent.ACTION_UP:
x2 = event.getX();
y2 = event.getY();
float deltaX = x2 - x1;
if (Math.abs(deltaX) > MIN_DISTANCE) {
if (x2 > x1)
mFragment.changeImage(-1); // move backwards
else
mFragment.changeImage(1); // move forwards
}
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_detail, menu);
return true;
}
}
public class DetailFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String PHOTOFRAGMENT_TAG = "PFTAG";
static final String DETAIL_URI = "URI";
private static final int DETAIL_LOADER = 0;
private ImageView mPhotoView, mCloseIcon;
private TextView mImageSizeView, mImageDateView;
public String mMemTitle, mMemDesc;
// data for sharing
private Long mImageID;
private ShareActionProvider mShareActionProvider;
private static final String[] DETAIL_COLUMNS = {
PhotoContract.PhotoEntry.COLUMN_IMAGE_ID,
PhotoContract.PhotoEntry.COLUMN_DISPLAY_NAME,
PhotoContract.PhotoEntry.COLUMN_DATA,
PhotoContract.PhotoEntry.COLUMN_DESC,
PhotoContract.PhotoEntry.COLUMN_DATE_TAKEN,
PhotoContract.PhotoEntry.COLUMN_DATE_ADDED,
PhotoContract.PhotoEntry.COLUMN_TITLE,
PhotoContract.PhotoEntry.COLUMN_SIZE,
PhotoContract.PhotoEntry.COLUMN_ORIENTATION,
PhotoContract.PhotoEntry.COLUMN_HEIGHT,
PhotoContract.PhotoEntry.COLUMN_WIDTH
};
static final int COL_IMAGE_ID = 0;
static final int COL_DISPLAY_NAME = 1;
static final int COL_DATA = 2;
static final int COL_DESC = 3;
static final int COL_DATE_TAKEN = 4;
static final int COL_DATE_ADDED = 5;
static final int COL_TITLE = 6;
static final int COL_SIZE = 7;
static final int COL_ORIENTATION = 8;
static final int COL_HEIGHT = 9;
static final int COL_WIDTH = 10;
static final int COL_THUMB_ID = 0;
static final int COL_THUMB_DATA = 1;
static final int COL_THUMB_IMAGE_ID = 2;
private Uri mUri;
private boolean noMedia = false;
private boolean mTwoPane = false;
public DetailFragment() {
setHasOptionsMenu(true); // only share button, though
}
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.detailfragment, menu);
MenuItem menuItem = menu.findItem(R.id.action_share);
mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem);
if (mFilePath != null) {
mShareActionProvider.setShareIntent(createShareForecastIntent());
}
}
public ImageView getPhotoView() {
return mPhotoView;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle arguments = getArguments();
if (arguments != null) {
mUri = arguments.getParcelable(DetailFragment.DETAIL_URI);
}
// get ID from URI
if (mUri != null) {
mImageID = PhotoContract.PhotoEntry.getImageIdFromUri(mUri);
}
View rootView = inflater.inflate(R.layout.fragment_detail, container, false);
mPhotoView = (ImageView) rootView.findViewById(R.id.pic_frame);
mImageSizeView = (TextView) rootView.findViewById(R.id.imgSize_textview);
mImageDateView = (TextView) rootView.findViewById(R.id.date_textview);
return rootView;
}
// user swipes; go to prev (direction = -1) or next (direction = 1) image:
public void changeImage(int direction) {
Cursor cursor = Utility.getCursor();
Long oldId = mImageID;
if (cursor == null)
return;
boolean result;
switch (direction) {
case -1:
if (cursor.isFirst()) return;
result = cursor.moveToPrevious();
break;
case 1:
if (cursor.isLast()) return;
result = cursor.moveToNext();
break;
default:
return;
}
Log.v("gallery", "DetailFragment.changeImage(" + direction + "), old id "
+ oldId + " => IMAGE_ID = " + cursor.getLong(PhotoFragment.COL_IMAGE_ID));
if (result) {
mUri = PhotoContract.PhotoEntry.buildPhotoUriWithId(cursor.getLong(PhotoFragment.COL_IMAGE_ID));
Utility.setPosition(cursor.getPosition()); // store position clicked!
// re-launch DetailActivity
Intent intent = new Intent(getActivity(), DetailActivity.class)
.setData(mUri);
startActivity(intent);
}
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (mUri != null) {
return new CursorLoader(getActivity(),
mUri, // URI passed in
DETAIL_COLUMNS, null, null, null);
} else {
mUri = PhotoContract.PhotoEntry.CONTENT_URI; // means, default (first) image
return new CursorLoader(getActivity(),
mUri, // "default URI": show first image...
DETAIL_COLUMNS, null, null, null);
}
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor data) {
if (data == null || !data.moveToFirst()) {
return;
}
String thumbData = data.getString(COL_DATA);
if (thumbData != null) {
Bitmap thumbBitmap;
try {
thumbBitmap = BitmapFactory.decodeFile(thumbData); // <--- OOM crash always occurs HERE (after ~5 swipes)
mPhotoView.setImageBitmap(thumbBitmap);
} catch (Exception e) {
return;
}
}
String metaDataText = data.getString(COL_WIDTH) + " x " + data.getString(COL_HEIGHT) + ", "
+ Math.round(data.getLong(COL_SIZE) / 1024.0) + "kb";
mImageSizeView.setText(metaDataText, TextView.BufferType.SPANNABLE);
mImageDateView.setText(DateFormat.format("d/M-yyyy",
new Date(data.getLong(COL_DATE_TAKEN))).toString());
if (mShareActionProvider != null)
mShareActionProvider.setShareIntent(createShareForecastIntent());
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
getLoaderManager().initLoader(DETAIL_LOADER, null, this);
super.onActivityCreated(savedInstanceState);
}
public void setTwoPane(boolean b) {
mTwoPane = b;
}
}