我想在主/详细视图中添加“上一个/下一个”功能。主人加载GridView(带缩略图),当点击时 - 通过URI(基本上包含相应的图像ID)启动DetailActivity。
我想要的是细节活动/片段能够直接跳转到上一个/下一个图像 - 而不必在主视图中来回移动。理想情况下,我想捕捉滑动手势。
我已经读过,最好的方法是使用ViewPager,使用FragmentStatePagerAdapter来保存子视图。但是,我想了解为什么我的第一次尝试不起作用(下面)。(另外,我希望避免重复修改代码,除非我必须这样做。)虽然我的尝试 工作,我只在几次滑动后得到OutOfMemory错误/崩溃:
ldap.base.provider.url.1=
ldap.base.dn.1=
ldap.security.principal.1=
ldap.security.credentials.1=
ldap.auth.search.filter.1=
ldap.user.mappings.1=
以下是相关的(我希望)代码。
请注意来自 MainActivity 的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)
来电,它会将图片正常加载到DetailFragment中。
此方法从 PhotoFragment 调用(通过回调/接口):GridView的public void onItemSelected(Uri contentUri)
处理程序,使用onClick
方法创建。这是正常的主图像加载图像,工作正常。
然后另一种方式: DetailActivity 捕获滑动手势,并调用DetailFragment中的onCreate
方法(通过成员对象/引用)。
DetailFragment的 changeImage
方法通过Utility类(容器对象)获取PhotoFragment中使用的游标。然后,它获取下一个/上一行并构造一个具有该ID的新URI,并使用该URI启动(重新启动?)DetailActivity。
这些重启似乎会累积内存,直到应用程序崩溃。崩溃(OOM错误)总是发生在DetailFragment的同一行,在我将Bitmap加载到PhotoView的部分:
changeImage
// OOM crash always occurs HERE (after ~5 swipes)
thumbBitmap = BitmapFactory.decodeFile(thumbData);
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;
}
}