Cuillère Posté(e) 14 septembre 2012 Share Posté(e) 14 septembre 2012 (modifié) Bonjour à tous ! Je souhaite partager mon travail pour afficher un Carrousel sur Android. Voilà le dépôt git qui contient le code source du layout ainsi qu'un projet d'exemple : git@bitbucket.org:abecker67/repo.git (https://bitbucket.org/abecker67/repo) J'attends vos remarques / impressions (voir votre participation) pour améliorer le projet ! ;) Modifié 19 septembre 2012 par Abecker Citer Lien vers le commentaire Partager sur d’autres sites More sharing options...
Cuillère Posté(e) 19 septembre 2012 Auteur Share Posté(e) 19 septembre 2012 (modifié) Mise à jour du carousel pour régler quelques problèmes (disponible sur le repo git) J'ajoute un screen du projet d'exemple. /*Copyright 2012 Arthur Becker Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ package com.abecker.androidutils.ui; import java.util.ArrayList; import android.content.Context; import android.graphics.Matrix; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Transformation; import android.widget.Adapter; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; public class CarouselLayout extends ViewGroup { String tag = getClass().getName(); Matrix mMatrix; // Animations private final int MAX_VELOCITY = 5000; private final int ANIMATION_DURATION = 250; private boolean isAnimating = false; private boolean isAnimatingToCenter = false; private float velocity = 0; private int velocityFactor = 1; private long lastCurTime = 0; private long curTime = 0; private long startTime = 0; private float startScrollOffset = 0; private float endScrollOffset = 0; private long elapsedTime = 0; // Available via getters and setters OnItemSelectedListener onItemSelectedListener; OnItemClickListener onItemClickListener; ChildStaticTransformationDatasource childStaticTransformationDatasource; Adapter adapter; boolean animateToCenter = false; private int mChildrenLimit = 2; private int mChildWidth = 100; private int mChildHeight = 100; private int mFirstIndex = Integer.MAX_VALUE; private int mLastIndex = Integer.MIN_VALUE; // View collector for recycle views private Collector mCollector; // Center X of the view private int mWidthCenter = 0; // Center Y of the view private int mHeightCenter = 0; // Currently selected index of the adapter private int selection = 0; // Children total width private int mChildrenTotalWidth = 0; // Scroll offset (0 when not animating) private float mScrollOffset = 0; // Center X of the first child (used for collecting old views) private float mMaxLeftChildCenter = 0; // Center X of the last child (used for collecting old views) private float mMaxRightChildCenter = 0; public CarouselLayout(Context context) { this(context, null); } public CarouselLayout(Context context, AttributeSet attrs) { super(context, attrs); mCollector = new Collector(); setStaticTransformationsEnabled(true); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // isAnimating = false; mGestureDetector.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP && !isAnimating) { startTime = System.currentTimeMillis(); startScrollOffset = mScrollOffset; if (velocityFactor == -1) endScrollOffset = 0; else endScrollOffset = mChildWidth - 1; if (animateToCenter) post(centerAnimation); } return true; } }); } // Animation onFling private Runnable animation = new Runnable() { @Override public void run() { curTime = System.currentTimeMillis(); elapsedTime = (int) (curTime - lastCurTime); scrollBy(velocity * ((float) elapsedTime / 1000f) * velocityFactor); decelerateAnimation(elapsedTime); lastCurTime = curTime; if (velocity <= 0) { startTime = System.currentTimeMillis(); startScrollOffset = mScrollOffset; if (velocityFactor == -1) endScrollOffset = 0; else endScrollOffset = mChildWidth - 1; if (animateToCenter) post(centerAnimation); else isAnimating = false; } else if (isAnimating) post(this); } }; // Animation onFling ends || onTouch up private Runnable centerAnimation = new Runnable() { @Override public void run() { isAnimatingToCenter = true; curTime = System.currentTimeMillis(); elapsedTime = curTime - startTime; if (elapsedTime > ANIMATION_DURATION) { // animation end if (velocityFactor == -1) scrollBy(-mScrollOffset); else scrollBy(mChildWidth - mScrollOffset); isAnimating = false; isAnimatingToCenter = false; notifyOnItemSelectedListener(); } else { float percent = (float) elapsedTime / (float) ANIMATION_DURATION; float nextFrameScrollOffset = endScrollOffset - (1f - percent) * (endScrollOffset - startScrollOffset); scrollBy(nextFrameScrollOffset - mScrollOffset); post(this); } } }; // Velocity deceleration alg private void decelerateAnimation(long elapsedTime) { velocity -= elapsedTime * 5; if (velocity < 0) velocity = 0; } // GestureDetector which manages (1)animation launch in onFling and // (2)onscroll GestureDetector mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { isAnimating = true; velocity = Math.min(Math.abs(velocityX), MAX_VELOCITY); if (velocityX < 0) velocityFactor = 1; else velocityFactor = -1; lastCurTime = System.currentTimeMillis(); post(animation); return true; }; public boolean onscroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (Math.abs(distanceX) > Math.abs(distanceY) && !isAnimating) { scrollBy(distanceX); if (distanceX > 0) velocityFactor = 1; else velocityFactor = -1; return true; } return false; }; public boolean onSingleTapConfirmed(MotionEvent e) { int index = 0; float x = e.getX() + mScrollOffset; float distanceFromCenter = x - mWidthCenter - mChildWidth / 2; index = (int) (distanceFromCenter / mChildWidth); if (distanceFromCenter > 0) index++; notifyOnItemClickListener(rawIndexToIndex(selection + index)); return true; }; }); // Scroll horizontaly the view by the specified offset. // It manages view recycling and mSelectedIndex updates private void scrollBy(float scroll) { int selectionOffset = (int) (scroll / (float) mChildWidth) + 1; if (selectionOffset > 1) { scroll = mChildWidth - 1; } mScrollOffset += scroll; int lastSelected = selection; childrenLayout(); invalidate(); for (int i = 0; i < getChildCount(); i++) { getChildAt(i).invalidate(); } if (!isAnimatingToCenter) { if (lastSelected == rawIndexToIndex(selection - 1)) { // recycle children on left View old = getChildAt(0); removeView(old); mCollector.collect(old); // add children on right View retrieve = mCollector.retrieve(); View child = adapter.getView(rawIndexToIndex(selection + mChildrenLimit), retrieve, this); addView(child); mLastIndex = rawIndexToIndex(mLastIndex + selectionOffset); mFirstIndex = rawIndexToIndex(mFirstIndex + selectionOffset); mScrollOffset -= mChildWidth * selectionOffset; maxCenterBounds(); } if (lastSelected == rawIndexToIndex(selection + 1)) { // recycle one child on right View old = getChildAt(getChildCount() - 1); removeView(old); mCollector.collect(old); // add one child on left View retrieve = mCollector.retrieve(); View child = adapter.getView(rawIndexToIndex(selection - mChildrenLimit), retrieve, this); addView(child, 0); mFirstIndex = rawIndexToIndex(mFirstIndex - 1); mLastIndex = rawIndexToIndex(mLastIndex - 1); mScrollOffset += mChildWidth; maxCenterBounds(); } // Log.d(tag, "first " + mFirstIndex + " last " + mLastIndex); } } private void notifyOnItemSelectedListener() { if (onItemSelectedListener != null) onItemSelectedListener.onItemSelected(null, this, selection, -1L); } private void notifyOnItemClickListener(int index) { if (onItemClickListener != null) onItemClickListener.onItemClick(null, this, index, -1L); } // Get a valide index with a raw index (e.g : rawIndex -1 becomes // adapter.getCount-1) private int rawIndexToIndex(int rawIndex) { int index = rawIndex; if (rawIndex < 0) index += adapter.getCount(); if (rawIndex >= adapter.getCount()) index -= adapter.getCount(); return index; } @Override protected void onLayout(boolean changed, int l, int t, int r, int B) { childrenLayout(); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(mChildWidth, mChildHeight); } // Layout all the children of the view private void childrenLayout() { mChildrenTotalWidth = mChildWidth * (mChildrenLimit * 2 + 1); final int startLeftForChildren = mWidthCenter - mChildrenTotalWidth / 2 - (int) mScrollOffset; int currentLeftForChildren = startLeftForChildren; final int childTop = mHeightCenter - mChildHeight / 2; final int childBottom = childTop + mChildHeight; mMaxLeftChildCenter = Float.MAX_VALUE; mMaxRightChildCenter = Float.MIN_VALUE; int curChildCenterWidth = 0; int selectedChildCenterWidth = 0; int selectedChildIndex = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(currentLeftForChildren, childTop, currentLeftForChildren + mChildWidth, childBottom); curChildCenterWidth = currentLeftForChildren + mChildWidth / 2; if (Math.abs(curChildCenterWidth - mWidthCenter) <= Math.abs(selectedChildCenterWidth - mWidthCenter)) { selectedChildIndex = i; selectedChildCenterWidth = curChildCenterWidth; } currentLeftForChildren += mChildWidth; } selection = rawIndexToIndex(mFirstIndex + selectedChildIndex); notifyOnItemSelectedListener(); maxCenterBounds(); } // Refreshes max center bounds of children private void maxCenterBounds() { mMaxLeftChildCenter = getChildAt(0).getLeft() + mChildWidth; mMaxRightChildCenter = getChildAt(getChildCount() - 1).getLeft() + mChildWidth; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); measureChildren(mChildWidth, mChildHeight); } private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } mWidthCenter = result / 2; return result; } private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } mHeightCenter = result / 2; return result; } public Adapter getAdapter() { return adapter; } public void setAdapter(Adapter adapter) { this.adapter = adapter; mFirstIndex = firstChildIndex(); mLastIndex = lastChildIndex(); for (int i = mFirstIndex; i != mLastIndex + 1; i = rawIndexToIndex(i + 1)) { Log.d(tag, "add view " + i); addView(adapter.getView(i, null, this)); } notifyOnItemSelectedListener(); childrenLayout(); } private int firstChildIndex() { int first = selection - mChildrenLimit; if (mMaxLeftChildCenter > 0) first--; if (first < 0) first += adapter.getCount(); return first; } private int lastChildIndex() { int last = selection + mChildrenLimit; if (mMaxRightChildCenter < getWidth()) last++; if (last >= adapter.getCount()) last -= adapter.getCount(); return last; } @Override protected boolean getChildStaticTransformation(View child, Transformation t) { if (childStaticTransformationDatasource == null) return false; t.clear(); float distanceFromCenter = child.getLeft() + mChildWidth / 2 - mWidthCenter; t.set(childStaticTransformationDatasource.getChildStaticTransformation(this, child, distanceFromCenter)); return true; /* return true; */ } public int getChildrenLimit() { return mChildrenLimit; } public void setChildrenLimit(int childrenLimit) { this.mChildrenLimit = childrenLimit; } public int getChildWidth() { return mChildWidth; } public void setChildWidth(int childWidth) { this.mChildWidth = childWidth; } public int getChildHeight() { return mChildHeight; } public void setChildHeight(int childHeight) { this.mChildHeight = childHeight; } public boolean isAnimateToCenter() { return animateToCenter; } public void setAnimateToCenter(boolean animateToCenter) { this.animateToCenter = animateToCenter; } public int getSelection() { return selection; } public void setSelection(int selection) { this.selection = selection; } public OnItemSelectedListener getOnItemSelectedListener() { return onItemSelectedListener; } public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) { this.onItemSelectedListener = onItemSelectedListener; } public OnItemClickListener getOnItemClickListener() { return onItemClickListener; } public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } public ChildStaticTransformationDatasource getChildStaticTransformationDatasource() { return childStaticTransformationDatasource; } public void setChildStaticTransformationDatasource(ChildStaticTransformationDatasource childStaticTransformationDatasource) { this.childStaticTransformationDatasource = childStaticTransformationDatasource; } private class Collector { ArrayList<View> mOldViews; public Collector() { mOldViews = new ArrayList<View>(); } public void collect(View v) { mOldViews.add(v); } public View retrieve() { if (mOldViews.size() == 0) return null; else return mOldViews.remove(0); } } public interface ChildStaticTransformationDatasource { public Transformation getChildStaticTransformation(ViewGroup parent, View child, float distanceFromCenter); } } Voilà le code pour ceux qui n'utilisent pas git ;) J'ai uploadé le projet de démo ici : terafiles.net/v-158008.html Modifié 19 septembre 2012 par Abecker 1 Citer Lien vers le commentaire Partager sur d’autres sites More sharing options...
Recommended Posts
Rejoignez la conversation
Vous pouvez poster maintenant et vous enregistrez plus tard. Si vous avez un compte, connectez-vous maintenant pour poster.