Related
I know that there are countless examples of this on the internet, but for some reason I can't get this to work in the particular code I'm working on, despite all the resources/answers I've read through. And I don't have anyone with Java skills around me to help out. So hopefully that changes here.
I'm updating an existing Android media picker plugin (for a cordova app) to make it show video thumbnails in addition to pictures in the device gallery. I'm stuck with a "non-static variable in static context" error, but I'm having a really hard time identifying what needs to be changed. Below is the meat of the code I have. I have removed some parts to hopefully focus on the relevant bits. Essentially, the error occurs inside decodeSampledBitmapFromUri when I'm trying to get the thumbnail of a video. The method I'm using is MediaStore.Video.Thumbnails.getThumbnail and its first argument is the context, which is where the error starts. You can see that in loadThumbnail I tried getting the context using cordova.getActivity() and then passing it to decodeSampledBitmapFromUri, but even inside loadThumbnail I'm still getting the non-static error. I'm not sure how to proceed from here (I'm very new to Java). This is the code (with some other parts stripped out because I think they're not relevant):
public class MediaPicker extends CordovaPlugin {
private static final String HEIGHT = "height";
private static final String COLUMNS = "columns";
private static final String SELECTION_LIMIT = "selectionLimit";
private MediaPickerView view;
public static int getResourceId(Context context, String group, String key) {
return context.getResources().getIdentifier(key, group, context.getPackageName());
}
public static int DP2PX(Context context, float dipValue) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics);
}
#Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
}
#Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
if (action.equals("display")) {
JSONObject store = data.getJSONObject(0);
double height = Double.parseDouble(store.getString(HEIGHT));
int columns = Integer.parseInt(store.getString(COLUMNS));
int selectionLimit = Integer.parseInt(store.getString(SELECTION_LIMIT));
display(height, columns, selectionLimit, callbackContext);
return true;
} else {
return false;
}
}
private void display(final double height, final int columns, final int selectionLimit, final CallbackContext callbackContext) {
cordova.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if (view == null) {
view = new MediaPickerView(cordova.getActivity());
}
view.setOptions(height, columns, selectionLimit, callbackContext);
view.load();
cordova.getActivity().addContentView(view,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
});
}
public static class MediaPickerView extends FrameLayout implements StateController {
private GridView grid;
private View balanceView;
private int selectionLimit;
private CallbackContext callbackContext;
private TreeMap<Integer, PictureInfo> selection = new TreeMap();
public MediaPickerView(Context context) {
super(context);
init();
}
public MediaPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
LayoutInflater.from(getContext()).inflate(getResourceId(getContext(), "layout", "view_media"), this);
grid = (GridView)findViewById(getResourceId(getContext(), "id", "gridView"));
balanceView = findViewById(getResourceId(getContext(), "id", "vBalancer"));
}
public void setOptions(double height, int columns, int selectionLimit, CallbackContext callbackContext) {
this.selectionLimit = selectionLimit;
this.callbackContext = callbackContext;
grid.setNumColumns(columns);
LinearLayout.LayoutParams glp = (LinearLayout.LayoutParams) grid.getLayoutParams();
glp.weight = (float) height;
LinearLayout.LayoutParams blp = (LinearLayout.LayoutParams) balanceView.getLayoutParams();
blp.weight = (float) (1 - height);
requestLayout();
}
public void load() {
final GridAdapter adapter = new GridAdapter(getContext(), this);
grid.setAdapter(adapter);
new Thread() {
#Override
public void run() {
final String[] columns = new String[]{
MediaStore.Video.VideoColumns._ID,
MediaStore.Video.VideoColumns.DATA,
MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME,
MediaStore.Video.VideoColumns.DISPLAY_NAME,
MediaStore.Video.VideoColumns.DATE_TAKEN,
MediaStore.Video.VideoColumns.MIME_TYPE,
MediaStore.Files.FileColumns.MEDIA_TYPE};
final String orderBy = MediaStore.Video.VideoColumns.DATE_TAKEN
+ " DESC";
final String selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
Uri queryUri = MediaStore.Files.getContentUri("external");
final Cursor cursor = getContext().getContentResolver().query(
queryUri,
columns,
selection, // Which rows to return (all rows)
null, // Selection arguments (none)
orderBy);
if (cursor.moveToFirst()) {
adapter.setCursor(cursor);
}
}
}.start();
}
}
public static class MediaCache extends LruCache<String, Bitmap> {
public MediaCache(int maxSize) {
super(maxSize);
}
#Override
protected int sizeOf(String key, Bitmap value) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return value.getAllocationByteCount();
}
return value.getByteCount();
}
}
public static class GridAdapter extends BaseAdapter implements AsyncPictureLoader{
private Cursor cursor;
private Executor executor = Executors.newFixedThreadPool(4);
private MediaCache pictureCache;
private int dataColumn;
private StateController stateController;
private ArrayList<PictureView> createdViews = new ArrayList<PictureView>();
public GridAdapter(Context context, StateController stateController) {
this.stateController = stateController;
int memClass = ( (ActivityManager)context.getSystemService( Context.ACTIVITY_SERVICE ) ).getMemoryClass();
int cacheSize = 1024 * 1024 * memClass / 10;
pictureCache = new MediaCache(cacheSize);
}
#Override
public int getCount() {
return cursor != null ? cursor.getCount() : 0;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
PictureView view;
if (convertView != null) {
view = (PictureView) convertView;
} else {
view = new PictureView(pictureCache, parent.getContext(), this, stateController);
createdViews.add(view);
}
view.load(position);
return view;
}
public void setCursor(Cursor cursor) {
this.cursor = cursor;
dataColumn = cursor
.getColumnIndex(MediaStore.Video.VideoColumns.DATA);
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
notifyDataSetChanged();
}
});
}
#Override
public void loadThumbnail(final PictureInfo pictureInfo, final AsyncPictureLoaderCallback callback) {
if (cursor == null) {
return;
}
executor.execute(new Runnable() {
#Override
public void run() {
if (cursor == null || pictureInfo.cancelled) {
return;
}
synchronized (cursor) {
cursor.moveToPosition(pictureInfo.position);
pictureInfo.uri = cursor.getString(dataColumn);
}
if (pictureInfo.uri == null) {
return;
}
synchronized (pictureCache) {
Bitmap cachedBitmap = pictureCache.get(pictureInfo.uri);
if (cachedBitmap != null) {
pictureInfo.thumbnail = cachedBitmap;
callback.onLoad(pictureInfo);
return;
}
}
int thumbSideSize = callback.getThumbnailSideSize();
if (thumbSideSize <= 0) {
thumbSideSize = 128;
}
// the next 4 variables are needed for videos, see https://stackoverflow.com/a/29555484/7987987
int type = cursor.getColumnIndex(MediaStore.Files.FileColumns.MEDIA_TYPE);
int tInt = cursor.getInt(type);
int colId = cursor.getColumnIndex(MediaStore.Video.VideoColumns._ID);
int id = cursor.getInt(colId);
pictureInfo.thumbnail = decodeSampledBitmapFromUri(pictureInfo.uri, thumbSideSize, thumbSideSize, tInt, id, cordova.getActivity());
if (pictureInfo.thumbnail != null) {
callback.onLoad(pictureInfo);
synchronized (pictureCache) {
pictureCache.put(pictureInfo.uri, pictureInfo.thumbnail);
}
} else {
}
}
});
}
private Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight, int typeInt, int id, Context context) {
Bitmap bm = null;
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
if (typeInt == 3) {
// this is a video, handle according to https://stackoverflow.com/a/29555484/7987987
// using BitmapFactory options as seen in the link above
options.inSampleSize = 4; // hardcoded for now until this works, then I'll make it dynamic
options.inPurgeable = true;
bm = MediaStore.Video.Thumbnails.getThumbnail(
context.getContentResolver(), id,
MediaStore.Video.Thumbnails.MINI_KIND, options);
} else {
// this is an image
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
options.inSampleSize = calculateSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
bm = BitmapFactory.decodeFile(path, options);
}
return bm;
}
public int calculateSampleSize(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) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
}
public interface AsyncPictureLoader {
void loadThumbnail(PictureInfo position, AsyncPictureLoaderCallback callback);
}
public interface AsyncPictureLoaderCallback {
void onLoad(PictureInfo picture);
int getThumbnailSideSize();
}
public interface StateController {
// stuff here that controls selection
}
public static class PictureInfo {
public int position;
public String uri;
public Bitmap thumbnail;
public volatile boolean cancelled = false;
}
public static class PictureView extends FrameLayout implements AsyncPictureLoaderCallback, CompoundButton.OnCheckedChangeListener {
private final AsyncPictureLoader loader;
private final CheckBox checkBox;
private final StateController stateController;
private final MediaCache pictureCache;
private ImageView vImage;
private PictureInfo pictureInfo;
public PictureView(MediaCache pictureCache, Context context, AsyncPictureLoader loader, StateController stateController) {
super(context);
this.pictureCache = pictureCache;
this.loader = loader;
this.stateController = stateController;
LayoutInflater.from(getContext()).inflate(getResourceId(getContext(), "layout", "view_media_item"), this);
vImage = (ImageView)findViewById(getResourceId(getContext(), "id", "vImage"));
checkBox = (CheckBox)findViewById(getResourceId(getContext(), "id", "checkBox"));
}
public void load(int position) {
if (pictureInfo != null) {
pictureInfo.cancelled = true;
}
pictureInfo = new PictureInfo();
pictureInfo.position = position;
pictureInfo.thumbnail = null;
pictureInfo.uri = null;
vImage.setImageResource(0);
vImage.setVisibility(INVISIBLE);
loader.loadThumbnail(pictureInfo, this);
updateSelection();
}
#Override
public void onLoad(PictureInfo picture) {
if (this.pictureInfo != picture) {
return;
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
vImage.setImageBitmap(pictureInfo.thumbnail);
vImage.setVisibility(VISIBLE);
}
});
}
}
}
I have a problem in my code. I want to autoplay first video from recyclerview in ExoPlayer, The player is working good on scroll but the first video does not play automatically
public class VideoPlayerRecyclerView extends RecyclerView {
private static final String TAG = "VideoPlayerRecyclerView";
private enum VolumeState {ON, OFF};
// ui
private ImageView thumbnail, volumeControl;
private ProgressBar progressBar;
private View viewHolderParent;
private FrameLayout frameLayout;
private PlayerView videoSurfaceView;
private SimpleExoPlayer videoPlayer;
// vars
private ArrayList<Status_Bakend> mediaObjects = new ArrayList<>();
private int videoSurfaceDefaultHeight = 0;
private int screenDefaultHeight = 0;
private Context context;
private int playPosition = -1;
private boolean isVideoViewAdded;
private RequestManager requestManager;
// controlling playback state
private VolumeState volumeState;
public VideoPlayerRecyclerView(#NonNull Context context) {
super(context);
init(context);
}
public VideoPlayerRecyclerView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(final Context context){
this.context = context.getApplicationContext();
Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
Point point = new Point();
display.getSize(point);
videoSurfaceDefaultHeight = point.x;
screenDefaultHeight = point.y;
videoSurfaceView = new PlayerView(this.context);
videoSurfaceView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM);
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
new DefaultTrackSelector(videoTrackSelectionFactory);
// 2. Create the player
videoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
// Bind the player to the view.
videoSurfaceView.setUseController(false);
videoSurfaceView.setPlayer(videoPlayer);
setVolumeControl(VolumeState.ON);
playVideo(true);
videoPlayer.setPlayWhenReady(true);
addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
Log.d(TAG, "onScrollStateChanged: called.");
if(thumbnail != null){ // show the old thumbnail
thumbnail.setVisibility(VISIBLE);
}
// There's a special case when the end of the list has been reached.
// Need to handle that with this bit of logic
if(!recyclerView.canScrollVertically(1)){
playVideo(true);
}
else{
playVideo(false);
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
addOnChildAttachStateChangeListener(new OnChildAttachStateChangeListener() {
#Override
public void onChildViewAttachedToWindow(View view) {
}
#Override
public void onChildViewDetachedFromWindow(View view) {
if (viewHolderParent != null && viewHolderParent.equals(view)) {
resetVideoView();
}
}
});
videoPlayer.addListener(new Player.EventListener() {
#Override
public void onTimelineChanged(Timeline timeline, #Nullable Object manifest, int reason) {
}
#Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
}
#Override
public void onLoadingChanged(boolean isLoading) {
}
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
switch (playbackState) {
case Player.STATE_BUFFERING:
Log.e(TAG, "onPlayerStateChanged: Buffering video.");
if (progressBar != null) {
progressBar.setVisibility(VISIBLE);
}
break;
case Player.STATE_ENDED:
Log.d(TAG, "onPlayerStateChanged: Video ended.");
videoPlayer.seekTo(0);
break;
case Player.STATE_IDLE:
break;
case Player.STATE_READY:
Log.e(TAG, "onPlayerStateChanged: Ready to play.");
if (progressBar != null) {
progressBar.setVisibility(GONE);
}
if(!isVideoViewAdded){
addVideoView();
}
break;
default:
break;
}
}
#Override
public void onRepeatModeChanged(int repeatMode) {
}
#Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
}
#Override
public void onPlayerError(ExoPlaybackException error) {
}
#Override
public void onPositionDiscontinuity(int reason) {
}
#Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
}
#Override
public void onSeekProcessed() {
}
});
}
public void playVideo(boolean isEndOfList) {
int targetPosition;
if(!isEndOfList){
int startPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
int endPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
// if there is more than 2 list-items on the screen, set the difference to be 1
if (endPosition - startPosition > 1) {
endPosition = startPosition + 1;
}
// something is wrong. return.
if (startPosition < 0 || endPosition < 0) {
return;
}
// if there is more than 1 list-item on the screen
if (startPosition != endPosition) {
int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);
targetPosition = startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
}
else {
targetPosition = startPosition;
}
}
else{
targetPosition = mediaObjects.size() - 1;
}
Toast.makeText(context, "playVideo: target position: " + targetPosition, Toast.LENGTH_SHORT).show();
// video is already playing so return
if (targetPosition == playPosition) {
return;
}
// set the position of the list-item that is to be played
playPosition = targetPosition;
if (videoSurfaceView == null) {
return;
}
// remove any old surface views from previously playing videos
videoSurfaceView.setVisibility(INVISIBLE);
removeVideoView(videoSurfaceView);
int currentPosition = targetPosition - ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
View child = getChildAt(currentPosition);
if (child == null) {
return;
}
VideoPlayerViewHolder holder = (VideoPlayerViewHolder) child.getTag();
if (holder == null) {
playPosition = -1;
return;
}
thumbnail = holder.thumbnail;
progressBar = holder.progressBar;
volumeControl = holder.volumeControl;
viewHolderParent = holder.itemView;
requestManager = holder.requestManager;
frameLayout = holder.itemView.findViewById(R.id.media_container);
videoSurfaceView.setPlayer(videoPlayer);
videoPlayer.addVideoListener(new VideoListener() {
#Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
frameLayout.getLayoutParams().height = (height * 2);
}
#Override
public void onRenderedFirstFrame() {
}
});
viewHolderParent.setOnClickListener(videoViewClickListener);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
context, Util.getUserAgent(context, "RecyclerView VideoPlayer"));
String mediaUrl = mediaObjects.get(targetPosition).getStatus_link();
if (mediaUrl != null) {
MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(mediaUrl));
videoPlayer.prepare(videoSource);
videoPlayer.setPlayWhenReady(true);
}
}
private OnClickListener videoViewClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
toggleVolume();
}
};
/**
* Returns the visible region of the video surface on the screen.
* if some are cut off, it will return less than the #videoSurfaceDefaultHeight
* #param playPosition
* #return
*/
private int getVisibleVideoSurfaceHeight(int playPosition) {
int at = playPosition - ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
Log.d(TAG, "getVisibleVideoSurfaceHeight: at: " + at);
View child = getChildAt(at);
if (child == null) {
return 0;
}
int[] location = new int[2];
child.getLocationInWindow(location);
if (location[1] < 0) {
return location[1] + videoSurfaceDefaultHeight;
} else {
return screenDefaultHeight - location[1];
}
}
// Remove the old player
private void removeVideoView(PlayerView videoView) {
ViewGroup parent = (ViewGroup) videoView.getParent();
if (parent == null) {
return;
}
int index = parent.indexOfChild(videoView);
if (index >= 0) {
parent.removeViewAt(index);
isVideoViewAdded = false;
viewHolderParent.setOnClickListener(null);
}
}
private void addVideoView(){
frameLayout.addView(videoSurfaceView);
isVideoViewAdded = true;
videoSurfaceView.requestFocus();
videoSurfaceView.setVisibility(VISIBLE);
videoSurfaceView.setAlpha(1);
}
private void resetVideoView(){
if(isVideoViewAdded){
removeVideoView(videoSurfaceView);
playPosition = -1;
videoSurfaceView.setVisibility(INVISIBLE);
}
}
public void releasePlayer() {
if (videoPlayer != null) {
videoPlayer.release();
videoPlayer = null;
}
viewHolderParent = null;
}
private void toggleVolume() {
if (videoPlayer != null) {
if (volumeState == VolumeState.OFF) {
Log.d(TAG, "togglePlaybackState: enabling volume.");
setVolumeControl(VolumeState.ON);
} else if(volumeState == VolumeState.ON) {
Log.d(TAG, "togglePlaybackState: disabling volume.");
setVolumeControl(VolumeState.OFF);
}
}
}
private void setVolumeControl(VolumeState state){
volumeState = state;
if(state == VolumeState.OFF){
videoPlayer.setVolume(0f);
animateVolumeControl();
}
else if(state == VolumeState.ON){
videoPlayer.setVolume(1f);
animateVolumeControl();
}
}
private void animateVolumeControl(){
if(volumeControl != null){
volumeControl.bringToFront();
if(volumeState == VolumeState.OFF){
requestManager.load(R.drawable.ic_favorite_border_black_24dp)
.into(volumeControl);
}
else if(volumeState == VolumeState.ON){
requestManager.load(R.drawable.ic_favorite_border_black_24dp)
.into(volumeControl);
}
volumeControl.animate().cancel();
volumeControl.setAlpha(1f);
volumeControl.animate()
.alpha(0f)
.setDuration(600).setStartDelay(1000);
}
}
public void setMediaObjects(ArrayList<Status_Bakend> mediaObjects){
this.mediaObjects = mediaObjects;
}
}
This is my code on recyclerview scroll working fine on scroll but I want to autoplay my first video in recyclerview without scrolling.
Just add this line after setAdapter to the recycler view.
recyclerView.smoothScrollBy(0, -1);
OR
recyclerView.smoothScrollBy(0, 1);
This will solve your problem.
You only need to make the first video play using postDelayed().
I think the reason is that the views of the RecyclerView items are all drawn on the layout and the video play process are duplicated.
I make whole video player app but stuck at mediacontroller. I want to show my own mediacontroller with different button, not the default one. It's 4rth day, i'm just trying to make my own mediacontroller but didn't succeed.
I succeed by doing it with surfaceview but i want to use it with videoview.
I try the following code.
public class MyMediaController extends MediaController {
private static final String TAG = "VideoControllerView";
MediaController.MediaPlayerControl mPlayer;
private Context mContext;
private View mAnchor;
private View mRoot;
private ProgressBar mProgress;
private TextView mEndTime, mCurrentTime;
private boolean mShowing;
private boolean mDragging;
private static final int sDefaultTimeout = 3000;
private static final int FADE_OUT = 1;
private static final int SHOW_PROGRESS = 2;
private boolean mUseFastForward;
private boolean mFromXml;
private boolean mListenersSet;
StringBuilder mFormatBuilder;
Formatter mFormatter;
private ImageButton mPauseButton;
private ImageButton mSubtitleButton;
private ImageButton mResizeButton;
private ImageButton mNextButton;
private ImageButton mPrevButton;
private final AccessibilityManager mAccessibilityManager;
public MyMediaController(Context context, AttributeSet attrs) {
super(context, attrs);
mRoot = null;
mContext = context;
mUseFastForward = true;
mFromXml = true;
mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
public MyMediaController(Context context, boolean useFastForward) {
super(context, useFastForward);
mUseFastForward = useFastForward;
mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
public MyMediaController(Context context) {
this(context, true);
mContext = context;
}
#Override
public void setMediaPlayer(MediaController.MediaPlayerControl player) {
mPlayer = player;
updatePausePlay();
}
#Override
public void setAnchorView(View view) {
mAnchor = view;
FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
removeAllViews();
View v = makeControllerView();
addView(v, frameParams);
}
protected View makeControllerView() {
LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mRoot = inflate.inflate(R.layout.media_controller_layout, null);
initControllerView(mRoot);
return mRoot;
}
private void initControllerView(View mRoot) {
mPauseButton = mRoot.findViewById(R.id.pause);
if (mPauseButton != null) {
mPauseButton.requestFocus();
mPauseButton.setOnClickListener(mPauseListener);
}
mResizeButton = mRoot.findViewById(R.id.resize);
if (mResizeButton != null) {
mResizeButton.requestFocus();
mResizeButton.setOnClickListener(mResizeListener);
}
mSubtitleButton = mRoot.findViewById(R.id.subtitle);
if (mSubtitleButton != null)
{
mSubtitleButton.requestFocus();
mSubtitleButton.setOnClickListener(mSubtitleListener);
}
mNextButton = mRoot.findViewById(R.id.next);
if (mNextButton != null ) {
mNextButton.requestFocus();
mNextButton.setOnClickListener(mNextListener);
}
mPrevButton = mRoot.findViewById(R.id.prev);
if (mPrevButton != null ) {
mPrevButton.requestFocus();
mPrevButton.setOnClickListener(mPrevListener);
}
mProgress = mRoot.findViewById(R.id.mediacontroller_progress);
if (mProgress != null) {
if (mProgress instanceof SeekBar) {
SeekBar seeker = (SeekBar) mProgress;
seeker.setOnSeekBarChangeListener(mSeekListener);
}
mProgress.setMax(1000);
}
mEndTime = mRoot.findViewById(R.id.time);
mCurrentTime = mRoot.findViewById(R.id.time_current);
mFormatBuilder = new StringBuilder();
mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
}
public final View.OnClickListener mPauseListener = new OnClickListener() {
#Override
public void onClick(View v) {
doPauseResume();
show(sDefaultTimeout);
}
};
private void doPauseResume() {
if (mPlayer == null) {
return;
}
if (mPlayer.isPlaying()) {
mPlayer.pause();
} else {
mPlayer.start();
}
updatePausePlay();
}
private void updatePausePlay() {
if (mRoot == null || mPauseButton == null)
return;
if (mPlayer.isPlaying())
mPauseButton.setImageResource(R.drawable.ic_pause);
else
mPauseButton.setImageResource(R.drawable.ic_play);
}
public final View.OnClickListener mResizeListener = new OnClickListener() {
#Override
public void onClick(View v) {
//Todo
Toast.makeText(mContext,"Resize is clicked",Toast.LENGTH_SHORT).show();
}
};
public final View.OnClickListener mNextListener = new OnClickListener() {
#Override
public void onClick(View v) {
//Todo
Toast.makeText(mContext,"NextBtn is clicked",Toast.LENGTH_SHORT).show();
}
};
public final View.OnClickListener mPrevListener = new OnClickListener() {
#Override
public void onClick(View v) {
//Todo
Toast.makeText(mContext,"PreviousBtn is clicked",Toast.LENGTH_SHORT).show();
}
};
public final View.OnClickListener mSubtitleListener = new OnClickListener() {
#Override
public void onClick(View v) {
//Todo
Toast.makeText(mContext,"subtitleBtn is clicked",Toast.LENGTH_SHORT).show();
}
};
private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
#Override
public void onStartTrackingTouch(SeekBar bar) {
show(3600000);
mDragging = true;
// By removing these pending progress messages we make sure
// that a) we won't update the progress while the user adjusts
// the seekbar and b) once the user is done dragging the thumb
// we will post one of these messages to the queue again and
// this ensures that there will be exactly one message queued up.
removeCallbacks(mShowProgress);
}
#Override
public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
if (!fromuser) {
// We're not interested in programmatically generated changes to
// the progress bar's position.
return;
}
long duration = mPlayer.getDuration();
long newposition = (duration * progress) / 1000L;
mPlayer.seekTo( (int) newposition);
if (mCurrentTime != null)
mCurrentTime.setText(stringForTime( (int) newposition));
}
#Override
public void onStopTrackingTouch(SeekBar bar) {
mDragging = false;
setProgress();
updatePausePlay();
show(sDefaultTimeout);
// Ensure that progress is properly updated in the future,
// the call to show() does not guarantee this because it is a
// no-op if we are already showing.
post(mShowProgress);
}
};
private int setProgress() {
if (mPlayer == null || mDragging) {
return 0;
}
int position = mPlayer.getCurrentPosition();
int duration = mPlayer.getDuration();
if (mProgress != null) {
if (duration > 0) {
// use long to avoid overflow
long pos = 1000L * position / duration;
mProgress.setProgress( (int) pos);
}
int percent = mPlayer.getBufferPercentage();
mProgress.setSecondaryProgress(percent * 10);
}
if (mEndTime != null)
mEndTime.setText(stringForTime(duration));
if (mCurrentTime != null)
mCurrentTime.setText(stringForTime(position));
return position;
}
private String stringForTime(int timeMs) {
int totalSeconds = timeMs / 1000;
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
mFormatBuilder.setLength(0);
if (hours > 0) {
return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
} else {
return mFormatter.format("%02d:%02d", minutes, seconds).toString();
}
}
private final Runnable mShowProgress = new Runnable() {
#Override
public void run() {
int pos = setProgress();
if (!mDragging && mShowing && mPlayer.isPlaying()) {
postDelayed(mShowProgress, 1000 - (pos % 1000));
}
}
};
#Override
public void show(int timeout) {
if (!mShowing && mAnchor != null) {
setProgress();
if (mPauseButton != null) {
mPauseButton.requestFocus();
}
FrameLayout.LayoutParams tlp = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM
);
addView(this, tlp);
mShowing = true;
}
updatePausePlay();
// cause the progress bar to be updated even if mShowing
// was already true. This happens, for example, if we're
// paused with the progress bar showing the user hits play.
post(mShowProgress);
if (timeout != 0 && !mAccessibilityManager.isTouchExplorationEnabled()) {
removeCallbacks(mFadeOut);
postDelayed(mFadeOut, timeout);
}
}
#Override
public boolean isShowing() {
return mShowing;
}
/**
* Remove the controller from the screen.
*/
#Override
public void hide() {
if (mAnchor == null)
return;
if (mShowing) {
try {
removeCallbacks(mShowProgress);
removeView(this);
} catch (IllegalArgumentException ex) {
Log.w("MediaController", "already removed");
}
mShowing = false;
}
}
private final Runnable mFadeOut = new Runnable() {
#Override
public void run() {
hide();
}
};
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
show(0);
break;
case MotionEvent.ACTION_UP:
show(sDefaultTimeout);
break;
case MotionEvent.ACTION_CANCEL:
hide();
break;
default:
break;
}
return true;
}
}
Plz someone suggest my the right way to do it. I realy need help.
So I am trying to make a Listview you can drag and drop the rows. I got it to work ok, but when I add an item to the Listview it breaks (here is what the bug looks like: http://youtu.be/VYRYh0Vwq6o). What is causing this problem and how do I fix it? Thanks in advance.
Heres my code:
ItemsArrayAdapter.xml
package benawad.com.todolist.adapters;
import android.content.Context;
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import benawad.com.todolist.NoteActivity;
import benawad.com.todolist.R;
public class ItemsArrayAdapter extends ArrayAdapter<String> {
public static final String TAG = ItemsArrayAdapter.class.getSimpleName();
Context mContext;
ArrayList<String> mArrayList;
boolean wantsSlash;
NoteActivity mNoteActivity;
EditText mNewItemText;
final int INVALID_ID = -1;
HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
public ItemsArrayAdapter
(NoteActivity context, ArrayList<String> arrayList, boolean slashes){
super(context, R.layout.item_row,arrayList);
mContext = context;
mArrayList = arrayList;
mNoteActivity = context;
wantsSlash = slashes;
update();
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_row, parent, false);
holder = new ViewHolder();
holder.itemName = (TextView)convertView.findViewById(R.id.itemText);
for (int i = 0; i < mArrayList.size(); ++i) {
mIdMap.put(mArrayList.get(i), i);
}
holder.delete = (ImageView)convertView.findViewById(R.id.delete_item);
holder.edit = (ImageView)convertView.findViewById(R.id.edit_item);
convertView.setTag(holder);
}
else{
holder = (ViewHolder) convertView.getTag();
}
holder.itemName.setText(mArrayList.get(position));
if(wantsSlash){
holder.itemName.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG);
}
holder.delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mNoteActivity.deleteItem(position);
}
});
holder.edit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mNoteActivity.editItem(position);
}
});
return convertView;
}
private static class ViewHolder {
TextView itemName;
ImageView edit;
ImageView delete;
}
#Override
public long getItemId(int position) {
Long idToReturn = (long) INVALID_ID;
if (position >= 0 && position <= mIdMap.size()-1) {
try {
String item = getItem(position);
idToReturn = (long) mIdMap.get(item);
}
catch(Exception e){}
}
return idToReturn;
}
// #Override
// public long getItemId(int position) {
// if (position < 0 || position >= mIdMap.size()) {
// return INVALID_ID;
// }
// String item = getItem(position);
// return mIdMap.get(item);
// }
public void update(){
for (int i = 0; i < mArrayList.size(); ++i) {
mIdMap.put(mArrayList.get(i), i);
}
}
#Override
public boolean hasStableIds() {
return true;
}
}
DynamicListView.java
package benawad.com.todolist;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import java.util.ArrayList;
import benawad.com.todolist.adapters.ItemsArrayAdapter;
public class DynamicListView extends ListView {
final static String TAG = DynamicListView.class.getSimpleName();
private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15;
private final int MOVE_DURATION = 150;
private final int LINE_THICKNESS = 15;
public ArrayList<String> mCheeseList;
private int mLastEventY = -1;
private int mDownY = -1;
private int mDownX = -1;
private int mTotalOffset = 0;
private boolean mCellIsMobile = false;
private boolean mIsMobileScrolling = false;
private int mSmoothScrollAmountAtEdge = 0;
private final int INVALID_ID = -1;
private long mAboveItemId = INVALID_ID;
private long mMobileItemId = INVALID_ID;
private long mBelowItemId = INVALID_ID;
private BitmapDrawable mHoverCell;
private Rect mHoverCellCurrentBounds;
private Rect mHoverCellOriginalBounds;
private final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private boolean mIsWaitingForScrollFinish = false;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
public DynamicListView(Context context) {
super(context);
init(context);
}
public DynamicListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public DynamicListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public void init(Context context) {
setOnItemLongClickListener(mOnItemLongClickListener);
setOnScrollListener(mScrollListener);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mSmoothScrollAmountAtEdge = (int)(SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density);
}
/**
* Listens for long clicks on any mItems in the listview. When a cell has
* been selected, the hover cell is created and set up.
*/
private OnItemLongClickListener mOnItemLongClickListener =
new OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int pos, long id) {
mTotalOffset = 0;
int position = pointToPosition(mDownX, mDownY);
int itemNum = position - getFirstVisiblePosition();
View selectedView = getChildAt(itemNum);
mMobileItemId = getAdapter().getItemId(position);
mHoverCell = getAndAddHoverView(selectedView);
selectedView.setVisibility(INVISIBLE);
mCellIsMobile = true;
updateNeighborViewsForID(mMobileItemId);
return true;
}
};
/**
* Creates the hover cell with the appropriate bitmap and of appropriate
* size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
* single time an invalidate call is made.
*/
private BitmapDrawable getAndAddHoverView(View v) {
int w = v.getWidth();
int h = v.getHeight();
int top = v.getTop();
int left = v.getLeft();
Bitmap b = getBitmapWithBorder(v);
BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
drawable.setBounds(mHoverCellCurrentBounds);
return drawable;
}
/** Draws a black border over the screenshot of the view passed in. */
private Bitmap getBitmapWithBorder(View v) {
Bitmap bitmap = getBitmapFromView(v);
Canvas can = new Canvas(bitmap);
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(LINE_THICKNESS);
paint.setColor(Color.BLACK);
can.drawBitmap(bitmap, 0, 0, null);
can.drawRect(rect, paint);
return bitmap;
}
/** Returns a bitmap showing a screenshot of the view passed in. */
private Bitmap getBitmapFromView(View v) {
Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas (bitmap);
v.draw(canvas);
return bitmap;
}
/**
* Stores a reference to the views above and below the item currently
* corresponding to the hover cell. It is important to note that if this
* item is either at the top or bottom of the list, mAboveItemId or mBelowItemId
* may be invalid.
*/
private void updateNeighborViewsForID(long itemID) {
int position = getPositionForID(itemID);
ItemsArrayAdapter adapter = ((ItemsArrayAdapter)getAdapter());
mAboveItemId = adapter.getItemId(position - 1);
mBelowItemId = adapter.getItemId(position + 1);
}
/** Retrieves the view in the list corresponding to itemID */
public View getViewForID (long itemID) {
int firstVisiblePosition = getFirstVisiblePosition();
ItemsArrayAdapter adapter = ((ItemsArrayAdapter)getAdapter());
for(int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
int position = firstVisiblePosition + i;
long id = adapter.getItemId(position);
if (id == itemID) {
return v;
}
}
return null;
}
/** Retrieves the position in the list corresponding to itemID */
public int getPositionForID (long itemID) {
View v = getViewForID(itemID);
if (v == null) {
return -1;
} else {
return getPositionForView(v);
}
}
/**
* dispatchDraw gets invoked when all the child views are about to be drawn.
* By overriding this method, the hover cell (BitmapDrawable) can be drawn
* over the listview's mItems whenever the listview is redrawn.
*/
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHoverCell != null) {
mHoverCell.draw(canvas);
}
}
#Override
public boolean onTouchEvent (MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownX = (int)event.getX();
mDownY = (int)event.getY();
mActivePointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER_ID) {
break;
}
int pointerIndex = event.findPointerIndex(mActivePointerId);
mLastEventY = (int) event.getY(pointerIndex);
int deltaY = mLastEventY - mDownY;
if (mCellIsMobile) {
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left,
mHoverCellOriginalBounds.top + deltaY + mTotalOffset);
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
handleCellSwitch();
mIsMobileScrolling = false;
handleMobileCellScroll();
return false;
}
break;
case MotionEvent.ACTION_UP:
touchEventsEnded();
break;
case MotionEvent.ACTION_CANCEL:
touchEventsCancelled();
break;
case MotionEvent.ACTION_POINTER_UP:
/* If a multitouch event took place and the original touch dictating
* the movement of the hover cell has ended, then the dragging event
* ends and the hover cell is animated to its corresponding position
* in the listview. */
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
touchEventsEnded();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
/**
* This method determines whether the hover cell has been shifted far enough
* to invoke a cell swap. If so, then the respective cell swap candidate is
* determined and the data set is changed. Upon posting a notification of the
* data set change, a layout is invoked to place the cells in the right place.
* Using a ViewTreeObserver and a corresponding OnPreDrawListener, we can
* offset the cell being swapped to where it previously was and then animate it to
* its new position.
*/
private void handleCellSwitch() {
final int deltaY = mLastEventY - mDownY;
int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffset + deltaY;
View belowView = getViewForID(mBelowItemId);
View mobileView = getViewForID(mMobileItemId);
View aboveView = getViewForID(mAboveItemId);
boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop());
boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getTop());
if (isBelow || isAbove) {
final long switchItemID = isBelow ? mBelowItemId : mAboveItemId;
View switchView = isBelow ? belowView : aboveView;
final int originalItem = getPositionForView(mobileView);
if (switchView == null) {
updateNeighborViewsForID(mMobileItemId);
return;
}
swapElements(mCheeseList, originalItem, getPositionForView(switchView));
((BaseAdapter) getAdapter()).notifyDataSetChanged();
mDownY = mLastEventY;
final int switchViewStartTop = switchView.getTop();
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.KITKAT){
mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);
} else{
mobileView.setVisibility(View.INVISIBLE);
switchView.setVisibility(View.VISIBLE);
}
updateNeighborViewsForID(mMobileItemId);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
View switchView = getViewForID(switchItemID);
mTotalOffset += deltaY;
int switchViewNewTop = switchView.getTop();
int delta = switchViewStartTop - switchViewNewTop;
switchView.setTranslationY(delta);
ObjectAnimator animator = ObjectAnimator.ofFloat(switchView,
View.TRANSLATION_Y, 0);
animator.setDuration(MOVE_DURATION);
animator.start();
return true;
}
});
}
}
private void swapElements(ArrayList arrayList, int indexOne, int indexTwo) {
Object temp = arrayList.get(indexOne);
arrayList.set(indexOne, arrayList.get(indexTwo));
arrayList.set(indexTwo, temp);
}
/**
* Resets all the appropriate fields to a default state while also animating
* the hover cell back to its correct location.
*/
private void touchEventsEnded () {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile|| mIsWaitingForScrollFinish) {
mCellIsMobile = false;
mIsWaitingForScrollFinish = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
// If the autoscroller has not completed scrolling, we need to wait for it to
// finish in order to determine the final location of where the hover cell
// should be animated to.
if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
mIsWaitingForScrollFinish = true;
return;
}
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mobileView.getTop());
ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds",
sBoundEvaluator, mHoverCellCurrentBounds);
hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
setEnabled(false);
}
#Override
public void onAnimationEnd(Animator animation) {
mAboveItemId = INVALID_ID;
mMobileItemId = INVALID_ID;
mBelowItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
setEnabled(true);
invalidate();
}
});
hoverViewAnimator.start();
} else {
touchEventsCancelled();
}
}
/**
* Resets all the appropriate fields to a default state.
*/
private void touchEventsCancelled () {
View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile) {
mAboveItemId = INVALID_ID;
mMobileItemId = INVALID_ID;
mBelowItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
invalidate();
}
mCellIsMobile = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
}
/**
* This TypeEvaluator is used to animate the BitmapDrawable back to its
* final location when the user lifts his finger by modifying the
* BitmapDrawable's bounds.
*/
private final static TypeEvaluator<Rect> sBoundEvaluator = new TypeEvaluator<Rect>() {
public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
return new Rect(interpolate(startValue.left, endValue.left, fraction),
interpolate(startValue.top, endValue.top, fraction),
interpolate(startValue.right, endValue.right, fraction),
interpolate(startValue.bottom, endValue.bottom, fraction));
}
public int interpolate(int start, int end, float fraction) {
return (int)(start + fraction * (end - start));
}
};
/**
* Determines whether this listview is in a scrolling state invoked
* by the fact that the hover cell is out of the bounds of the listview;
*/
private void handleMobileCellScroll() {
mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
}
/**
* This method is in charge of determining if the hover cell is above
* or below the bounds of the listview. If so, the listview does an appropriate
* upward or downward smooth scroll so as to reveal new mItems.
*/
public boolean handleMobileCellScroll(Rect r) {
int offset = computeVerticalScrollOffset();
int height = getHeight();
int extent = computeVerticalScrollExtent();
int range = computeVerticalScrollRange();
int hoverViewTop = r.top;
int hoverHeight = r.height();
if (hoverViewTop <= 0 && offset > 0) {
smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
public void setCheeseList(ArrayList<String> cheeseList) {
mCheeseList = cheeseList;
}
/**
* This scroll listener is added to the listview in order to handle cell swapping
* when the cell is either at the top or bottom edge of the listview. If the hover
* cell is at either edge of the listview, the listview will begin scrolling. As
* scrolling takes place, the listview continuously checks if new cells became visible
* and determines whether they are potential candidates for a cell swap.
*/
private OnScrollListener mScrollListener = new OnScrollListener () {
private int mPreviousFirstVisibleItem = -1;
private int mPreviousVisibleItemCount = -1;
private int mCurrentFirstVisibleItem;
private int mCurrentVisibleItemCount;
private int mCurrentScrollState;
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mCurrentFirstVisibleItem = firstVisibleItem;
mCurrentVisibleItemCount = visibleItemCount;
mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem
: mPreviousFirstVisibleItem;
mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount
: mPreviousVisibleItemCount;
checkAndHandleFirstVisibleCellChange();
checkAndHandleLastVisibleCellChange();
mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
mPreviousVisibleItemCount = mCurrentVisibleItemCount;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
mScrollState = scrollState;
isScrollCompleted();
}
/**
* This method is in charge of invoking 1 of 2 actions. Firstly, if the listview
* is in a state of scrolling invoked by the hover cell being outside the bounds
* of the listview, then this scrolling event is continued. Secondly, if the hover
* cell has already been released, this invokes the animation for the hover cell
* to return to its correct position after the listview has entered an idle scroll
* state.
*/
private void isScrollCompleted() {
if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
if (mCellIsMobile && mIsMobileScrolling) {
handleMobileCellScroll();
} else if (mIsWaitingForScrollFinish) {
touchEventsEnded();
}
}
}
/**
* Determines if the listview scrolled up enough to reveal a new cell at the
* top of the list. If so, then the appropriate parameters are updated.
*/
public void checkAndHandleFirstVisibleCellChange() {
if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForID(mMobileItemId);
handleCellSwitch();
}
}
}
/**
* Determines if the listview scrolled down enough to reveal a new cell at the
* bottom of the list. If so, then the appropriate parameters are updated.
*/
public void checkAndHandleLastVisibleCellChange() {
int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
if (currentLastVisibleItem != previousLastVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForID(mMobileItemId);
handleCellSwitch();
}
}
}
};
}
Ran out of characters here is NoteActivity.java on pastebin.com: http://pastebin.com/MgaFXHb6
If you need to see any other code let me know.
I'm currently implementing my own custom listview. I idea is that a longclick on a list item will scale the surrounding visible views to a smaller value scale and leave the the current view normal, whilst expanding a view below the current selected view. Once the user touches anywhere around the selected view, it will scale everything back to normal.
Unfortunately, there's an issue whereby the last view maybe partially hidden during the scale animation and so doesn't scale back to normal. So when you try to gain focus to the rest of the Listview by touching outside the selected list item, it remains a smaller scale.
I've tried a few approaches, from using ViewTreeObserver to storing the positions of the list items to make sure that they get scaled, but nothing has worked. Below is my custom listview:
public class ZoomListView extends ListView implements AdapterView.OnItemLongClickListener {
private static final String TAG = ZoomListView.class.getName();
private int _xPos;
private int _yPos;
private int _pointerId;
private Rect _viewBounds;
private boolean _isZoomed;
private OnItemClickListener _listner;
private OnItemFocused _onItemFocusedLis;
private int _expandingViewHeight = 0;
private int _previousFocusedViewHeight;
public interface OnItemFocused {
/**
* This interface can be used to be notified when a particular item should be disabled, or is currently not focused
* #param position
*/
public void onItemOutOfFocus(int position, boolean status_);
public View onItemFocused(View focusedView_, int listViewPosition_, long uniqueId_);
}
public ZoomListView(Context context_) {
super(context_);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs) {
super(context_, attrs);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs, int defStyle) {
super(context_, attrs, defStyle);
init(context_);
}
#Override
public void setOnItemClickListener(OnItemClickListener listener) {
if(!(listener == null)){
_listner = listener;
}
super.setOnItemClickListener(listener);
}
private void init(Context context_){
setOnItemLongClickListener(this);
}
public void setOnItemDisableListener(OnItemFocused listener_){
_onItemFocusedLis = listener_;
}
private void scaleChildViews(long rowId_, int itemPos_, float scale, boolean shouldEnable){
if (_isZoomed) {
getParent().requestDisallowInterceptTouchEvent(true);
}
int firstVisiblePosition = getFirstVisiblePosition();
int pos = pointToPosition(_xPos, _yPos);
int positionOrg = pos - firstVisiblePosition;
scaleAllVisibleViews(positionOrg, scale, shouldEnable);
}
private void scaleAllVisibleViews(final int clickedItemPosition_, final float scale_, final boolean shouldEnable_) {
Animation scaleAnimation;
if(_isZoomed){
scaleAnimation = getZoomAnimation(1f, 0.8f, 1f, 0.8f);
}else{
scaleAnimation = getZoomAnimation(0.8f, 1f, 0.8f, 1f);
}
int firstVisiblePosition = getFirstVisiblePosition();
int count = getChildCount();
for (int i = 0; i < count; i++) {
int pos = i;
if (_isZoomed) {
if (getAdapter().getItemId(clickedItemPosition_) != getAdapter().getItemId(pos)) {
scaleView(pos, scale_, shouldEnable_, scaleAnimation);
}else{
displayExpandingView(pos, clickedItemPosition_);
}
} else {
View view = getChildAt(pos);
View viewToShow = _onItemFocusedLis.onItemFocused(view, pos, getAdapter().getItemId(clickedItemPosition_));
if(viewToShow != null){
viewToShow.setVisibility(GONE);
}
scaleView(pos, scale_, shouldEnable_, scaleAnimation);
}
}
}
private void displayExpandingView(int position_, int clickedItemPosition_){
View view = getChildAt(position_);
if(view != null){
Log.v(TAG, "view is valid");
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, getAdapter().getItemId(clickedItemPosition_));
viewToShow.setVisibility(VISIBLE);
Animation flip = new CyclicFlipAnimation(50f);
flip.setDuration(500);
viewToShow.startAnimation(flip);
if(_expandingViewHeight <= 0){
viewToShow.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
_expandingViewHeight = viewToShow.getMeasuredHeight();
Log.v(TAG, "expanding view hieght is + " + _expandingViewHeight);
}
}
}
private Animation getZoomAnimation(float fromX_, float toX_, float fromY_, float toY_){
Animation scaleAnimation = new ScaleAnimation(
fromX_, toX_,
fromY_, toY_,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setFillAfter(true);
scaleAnimation.setDuration(500);
return scaleAnimation;
}
private void scaleView(int position_, float scale_, boolean shouldEnable_, Animation animation_){
View view = getChildAt(position_);
if (view != null) {
view.startAnimation(animation_);
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemOutOfFocus(position_, shouldEnable_);
}
}
}
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
_isZoomed = true;
scaleChildViews(l, i, 0.8f, false);
return true;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
_xPos = (int) event.getX();
_yPos = (int) event.getY();
_pointerId = event.getPointerId(0);
if (_isZoomed) {
if (!_viewBounds.contains(_xPos, _yPos)) {
_isZoomed = false;
scaleChildViews(1, 1, 1f, true);
}
return false;
}
int position = pointToPosition(_xPos, _yPos);
int childNum = (position != INVALID_POSITION) ? position - getFirstVisiblePosition() : -1;
View itemView = (childNum >= 0) ? getChildAt(childNum) : null;
if (itemView != null) {
_viewBounds = getChildViewRect(this, itemView);
}
break;
}
return super.onTouchEvent(event);
}
private Rect getChildViewRect(View parentView, View childView) {
final Rect childRect = new Rect(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom() + _expandingViewHeight);
if (parentView == childView) {
return childRect;
}
ViewGroup parent = (ViewGroup) childView.getParent();
while (parent != parentView) {
childRect.offset(parent.getLeft(), parent.getTop());
childView = parent;
parent = (ViewGroup) childView.getParent();
}
return childRect;
}
}
Any idea's on what maybe wrong, thanks.
To fix this issue I ended up adding a ACTION_MOVE case statement in my onTouch method. So as long as the list items were focused again, when the list performed a move i.e. scrolled the list item, the remaining list items are restored to their original size. To achieve this you need to maintain a map/collection of id's that correspond to your list items and iterate over them once the user scrolls.
Heres the full somwhat functiong implementation:
package com.sun.tweetfiltrr.zoomlistview;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import com.sun.tweetfiltrr.animation.CyclicFlipAnimation;
import java.util.HashMap;
import java.util.Map;
/**
*/
public class ZoomListView extends ListView implements AdapterView.OnItemLongClickListener {
private static final String TAG = ZoomListView.class.getName();
private int _xPos;
private int _yPos;
private int _pointerId;
private Rect _viewBounds;
private boolean _isZoomed;
private OnItemClickListener _listner;
private OnItemFocused _onItemFocusedLis;
private int _expandingViewHeight = 0;
private int _previousFocusedViewHeight;
private OnScrollListener _onScrollListener;
private boolean _shouldPerformScrollAnimation;
final private Map<Long, PropertyHolder> _itemIDToProperty = new HashMap<Long, PropertyHolder>();
private long _currentFocusedId;
public interface OnItemFocused {
/**
* This interface can be used to be notified when a particular item should be disabled, or is currently not focused
* #param position
*/
public void onItemScaleOut(int position, View view, boolean status_);
public void onItemRestore(int position, View view, boolean status_);
public View onItemFocused(View focusedView_, int listViewPosition_, long uniqueId_);
}
public ZoomListView(Context context_) {
super(context_);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs) {
super(context_, attrs);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs, int defStyle) {
super(context_, attrs, defStyle);
init(context_);
}
private void init(Context context_){
setOnItemLongClickListener(this);
}
public void setOnItemDisableListener(OnItemFocused listener_){
_onItemFocusedLis = listener_;
}
private void scaleChildViews(long rowId_, int itemPos_, float scale, boolean shouldEnable){
if (_isZoomed) {
getParent().requestDisallowInterceptTouchEvent(true);
}
scaleAllVisibleViews(shouldEnable);
}
private void scaleAllVisibleViews(final boolean shouldEnable_) {
final ListAdapter adapter = getAdapter();
Animation scaleAnimation;
if(_isZoomed){
scaleAnimation = getZoomAnimation(1f, 0.6f, 1f, 0.6f);
}else{
scaleAnimation = getZoomAnimation(0.6f, 1f, 0.6f, 1f);
}
int count = getChildCount();
for (int i = 0; i < count; i++) {
applyAnimation(i, adapter, shouldEnable_, scaleAnimation);
}
}
private void applyAnimation(int position_, ListAdapter adapter_, boolean shouldEnable_, Animation animation_){
if (_isZoomed) {
if (_currentFocusedId != adapter_.getItemId(position_)) {
scaleView(position_, shouldEnable_, animation_);
}else{
displayExpandingView(position_, adapter_);
}
}else{
if(_currentFocusedId != getAdapter().getItemId(position_)){
scaleView(position_, shouldEnable_, animation_);
}else{
View view = getChildAt(position_);
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, getAdapter().getItemId(position_));
if(viewToShow != null){
viewToShow.setVisibility(GONE);
}
}
_itemIDToProperty.remove(getAdapter().getItemId(position_));
}
}
private void displayExpandingView(int position_, ListAdapter adapter_){
View view = getChildAt(position_);
if(view != null){
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, adapter_.getItemId(position_));
viewToShow.setVisibility(VISIBLE);
Animation flip = new CyclicFlipAnimation(60f);
flip.setDuration(1000);
viewToShow.startAnimation(flip);
if(_expandingViewHeight <= 0){
viewToShow.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
_expandingViewHeight = viewToShow.getMeasuredHeight();
}
}
}
private Animation getZoomAnimation(float fromX_, float toX_, float fromY_, float toY_){
Animation scaleAnimation = new ScaleAnimation(
fromX_, toX_,
fromY_, toY_,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.ABSOLUTE, 0.7f);
scaleAnimation.setFillAfter(true);
scaleAnimation.setDuration(500);
return scaleAnimation;
}
private void scaleView(int position_, boolean shouldEnable_, Animation animationScale_ ){
View view = getChildAt(position_);
ListAdapter adapter = getAdapter();
long id = adapter.getItemId(position_);
view.startAnimation(animationScale_);
PropertyHolder holder = _itemIDToProperty.get(id);
if (holder == null) {
holder = new PropertyHolder((int) view.getTop(), (int) (view.getBottom()));
_itemIDToProperty.put(id, holder);
}
int h = view.getHeight();
if (_isZoomed) {
view.animate().translationYBy((h * 0.5f)).setDuration(500).start();
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemScaleOut(position_, view, shouldEnable_);
}
} else {
view.animate().translationYBy(-(h * 0.5f )).setDuration(500).start();
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemRestore(position_, view, shouldEnable_);
}
}
}
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
_isZoomed = true;
int firstVisiblePosition = getFirstVisiblePosition();
int pos = pointToPosition(_xPos, _yPos);
int positionOrg = pos - firstVisiblePosition;
_currentFocusedId = getAdapter().getItemId(positionOrg);
scaleChildViews(l, i, 0.8f, false);
return true;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
_xPos = (int) event.getX();
_yPos = (int) event.getY();
_pointerId = event.getPointerId(0);
if (_isZoomed) {
if (!_viewBounds.contains(_xPos, _yPos)) {
_isZoomed = false;
scaleChildViews(1, 1, 1f, true);
}
return false;
}
int position = pointToPosition(_xPos, _yPos);
int childNum = (position != INVALID_POSITION) ? position - getFirstVisiblePosition() : -1;
View itemView = (childNum >= 0) ? getChildAt(childNum) : null;
if (itemView != null) {
_viewBounds = getChildViewRect(this, itemView);
}
break;
case MotionEvent.ACTION_MOVE:
if (!_isZoomed) {
animateRemaining();
}
break;
}
return super.onTouchEvent(event);
}
private void animateRemaining(){
if(!_itemIDToProperty.isEmpty()){
for(int i = 0; i < getChildCount(); i++){
long id = getAdapter().getItemId(i);
PropertyHolder n = _itemIDToProperty.get(id);
if(n != null){
if(_currentFocusedId != getAdapter().getItemId(i)){
scaleView(i, true, getZoomAnimation(0.6f, 1, 0.6f, 1f));
}else{
Log.v(TAG, "not translating for " + getAdapter().getItemId(i)+ " for id " + _currentFocusedId);
}
_itemIDToProperty.remove(id);
}
}
}
}
private Rect getChildViewRect(View parentView, View childView) {
final Rect childRect = new Rect(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom() + _expandingViewHeight);
if (parentView == childView) {
return childRect;
}
ViewGroup parent = (ViewGroup) childView.getParent();
while (parent != parentView) {
childRect.offset(parent.getLeft(), parent.getTop());
childView = parent;
parent = (ViewGroup) childView.getParent();
}
return childRect;
}
private class PropertyHolder{
int _top;
int _bot;
private PropertyHolder(int top_, int bot_){
_top = top_;
_bot = bot_;
}
}
}
It's far from perfect but its a starting point.