したがって、私が現在直面している問題は次のとおりです。魚眼ビューを作成したいです(そのため、アイテムを含むビューで、真ん中のアイテムが大きく、MAC OS アイテムバーまたは他の場所に表示されるような他のアイテムよりも大きくなります) )。
これまでのところ、それを実現するために HorizontalScrollView を拡張しました。いくつかのテストの後、すべてが正常に機能しているように見えたので、スクロールビューを移動すると、アイテムの位置に応じてアイテムが適切に更新されます。
ただし、スクロールビューが境界に対して「跳ね返る」場合に発生する問題が 1 つあります。したがって、ScrollView が高速に移動する場合、「getScrollX()」は 0 より小さい値または最大境界よりも大きい値を返します。その後、アイテムのサイズが変更されなくなりますが、これは非常に奇妙です。
コードを確認したところ、アイテムのサイズ変更メソッドが呼び出されましたが、アイテムが更新されなくなった理由がわかりません。
ScrollView クラスは次のようになります。
public class HorizontalFishEyeView extends HorizontalScrollView
{
//****************************************************************************************************
// enums
//****************************************************************************************************
public enum ONTOUCHEND
{
//-----undefined value
NONE,
//-----meaning to keep scrolling after the touch event ended
SCROLL_ON_END,
//-----meaning to continue to the next base after the touch event ended
CONTINUE_ON_END,
//-----meaning to switch the element after the on touch event ended
CHANGE_ELEMENT_ON_END
}
public enum MODE
{
//-----none mode meaning the view is not scrolling or doing the finish animation, thus being idle
NONE,
//-----scroll meaning the view is scrolling after an accelleration
SCROLL,
//-----finish meaning the view is doing the finish animation to move to the actual element
FINISH,
}
//****************************************************************************************************
// variables
//****************************************************************************************************
//-----determines if the view will continue when the finish animation is played
private boolean m_bContinueOnClick = false;
//-----time for the scroll animation
private long m_nScrollAnimationTime = 0;
//------the multiplier to be used for the velocity on initaial start
private float m_fVelocityMultiplier = 1.0f;
//-----the direction of the velocity however the reverse value to use it in conjunction with the decrement
private int m_nVelocityDirectionReverse = 0;
//------the velocity provided when the event has ended
private float m_fVelocity = 0.0f;
//-----determines hwo much the velocity decreases per millisecond
private float m_fVelocityDecrement = 0.001f;
//-----time when the touch event was started
private long m_nStartTime = 0;
//-----the x position of the touch event
private float m_nXPosition = -1.0f;
//-----determines when the animation for moving shall be canceled
private final float m_fVelocityThreshold = 0.25f;
//-----determines the time, e.g the start time of the animation and stores the time each time the draw method is called
//-----while the finish animation is in progress
private long m_nFinishAnimationTime = 0;
//-----determines how much pixel the layout will be moved for each millisecond passed,
//-----while the finish animation is playing
private double m_dFinishAnimationIncrements = 0.0;
//-----the actually duration of the finish animation, this value is dependent of the difference distance
//-----which the view has to be moved, so at max this will bei 0.5 times m_nFinishAnimationTime
private int m_nFinishAnimationDuration = 0;
//-----determines the distance which the view has to be moved in order to set the selected element into focus
private int m_nFinishRemainingDiff = 0;
//-----the position which the view will have as its left margin,
//-----this value us determined when the user lets go of the view
private int m_nFinishTargetPosition = 0;
//-----the animation time the finish animation when the user lets go of the view
private int m_nAnimationTime = 0;
//-----the position of the element which is closest to the selector, thus it actually is the selected element
private FishEyeItem m_nClosestElement = null;
//-----scalefactor used to calculate the min item size, thus m_nItemSizeMin = nItemSize * m_fItemSizeMaxScale
private float m_fItemSizeMinScale = -1;
//-----the size of the image of the item when not selected
private int m_nItemSizeMin = -1;
//-----scalefactor used to calculate the max item size, thus m_nItemSizeMax = nItemSize * m_fItemSizeMaxScale
private float m_fItemSizeMaxScale = -1;
//-----the size of the image of the item when selected
private int m_nItemSizeMax = -1;
//-----the difference in item size between the max and the min value
private int m_nItemSizeDiff = -1;
//-----determines at which distance the item size will always be min
private int m_nMaxDiff = 0;
//-----the middel point of the view, used to determine the distance of an item and thus its size
private int m_nReferenceX = 0;
//-----event listener attached to this view
protected AnimationEventListener m_oEventListener;
//-----this factor is multiplied by the velocity up on the UP event to determine the remaining scroll
private float m_fVelocityScaleFactor = 0.25f;
//-----the mode in which the fisheyeview currently is
private MODE m_eMode = MODE.NONE;
//-----the reference to the one and only child in the scrollview, as it should be
private LinearLayout m_oChild = null;
//-----number of items whose bitmap will still be available even if they are not visible
private int m_nItemBuffer = 2;
//-----activity to use
private Activity m_oActivity = null;
//-----scalefactor to use
private float m_fScaleFactor = 1.0f;
//-----determines if the itemsize is stable thus each item is the same size, used to prevent unnecessary calculations
private boolean m_bItemSizeStable = false;
//****************************************************************************************************
// constructor
//****************************************************************************************************
public HorizontalFishEyeView(Context context)
{
super(context);
}
public HorizontalFishEyeView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
//****************************************************************************************************
// public
//****************************************************************************************************
/**
* this method will set up the view before it is used, thus it needs to be called before
* @param oActivity
* @param fItemSizeMinScale
* @param fItemSizeMaxScale
* @param fScaleFactor
* @param bItemSizeStable
*/
public void Initialize(Activity oActivity, float fItemSizeMinScale, float fItemSizeMaxScale, float fScaleFactor, boolean bItemSizeStable)
{
try
{
m_oActivity = oActivity;
m_nReferenceX = (int)(getWidth()*0.5f);
m_fItemSizeMaxScale = Math.min(1.0f, Math.max(0.0f, fItemSizeMaxScale));
m_fItemSizeMinScale = Math.min(1.0f, Math.max(0.0f, fItemSizeMinScale));
m_bItemSizeStable = bItemSizeStable;
m_fScaleFactor = fScaleFactor;
}
catch(Exception e)
{
Log.d("Initialize", e.toString());
}
}
public void Clear()
{
try
{
if(m_oChild!=null)
{
for(int i=0;i<m_oChild.getChildCount();i++)
{
View oChild = m_oChild.getChildAt(i);
if(oChild instanceof FishEyeItem)
{
NetcoMethods.RecycleImageView(((FishEyeItem)oChild).GetImageView());
}
}
m_oChild.removeAllViews();
}
}
catch(Exception e)
{
Log.d("Clear", e.toString());
}
}
public void AddItem(FishEyeItem oItem, LinearLayout.LayoutParams oParams)
{
try
{
if(m_oChild!=null)
{
m_oChild.addView(oItem, oParams);
}
}
catch(Exception e)
{
Log.d("AddItem", e.toString());
}
}
public MODE GetMode()
{
return m_eMode;
}
public void Reinitialize()
{
}
public void Deinitialize()
{
}
/**
* adds an animation listener to the list
* @param listener
*/
public void SetAnimationEventListener(AnimationEventListener listener)
{
m_oEventListener = listener;
}
public void ScrollTo()
{
try
{
}
catch(Exception e)
{
Log.d("ScrollTo", e.toString());
}
}
public LinearLayout GetChild()
{
return m_oChild;
}
//****************************************************************************************************
// private
//****************************************************************************************************
/**called when the size was calculated*/
private void SizeCalculated(Object o)
{
try
{
if(m_oEventListener!=null)
{
m_oEventListener.AnimationEvent(o);
}
}
catch(Exception e)
{
Log.d("AnimationEndEvent", e.toString());
}
}
/**
* calculates the sizes for an item, if m_bItemSizeStable is set to true this will only be done once
* @param nItemSize, the size of the item which will be used
*/
private void CalulateItemSize(int nItemSize)
{
try
{
if(!m_bItemSizeStable)
{
m_nItemSizeMax = (int)(nItemSize * m_fItemSizeMaxScale);
m_nItemSizeMin = (int)(nItemSize * m_fItemSizeMinScale);
m_nItemSizeDiff = m_nItemSizeMax - m_nItemSizeMin;
m_nMaxDiff = nItemSize*2;
}
else if(m_nItemSizeMax==-1)
{
m_nItemSizeMax = (int)(nItemSize * m_fItemSizeMaxScale);
m_nItemSizeMin = (int)(nItemSize * m_fItemSizeMinScale);
m_nItemSizeDiff = m_nItemSizeMax - m_nItemSizeMin;
m_nMaxDiff = nItemSize*2;
}
}
catch(Exception e)
{
Log.d("CalculateItemSize", e.toString());
}
}
/**
* this method will Resize and item in the view depending on its position
* @param oItem the item which shall be resized
* @param nDiff the distance of the item from the middle of he view
* @param nCurrentClosestDiff the currently know closest distance, if the item is closer the given nDiff will be used
*/
private void DeterminenSize(FishEyeItem oItem, int nDiff, int nCurrentClosestDiff)
{
try
{
if(oItem!=null)
{
CalulateItemSize(oItem.getWidth());
//-----check if the item can be resized
if(oItem.GetCanBeResized())
{
//System.out.println("Item is "+ oItem.GetImagePath());
//System.out.println("Item Diff is "+ nDiff);
//-----items is in range
if(nDiff<m_nMaxDiff)
{
//-----determine whether this element is closer to the selector then the previously known
if(nCurrentClosestDiff==-1)
{
nCurrentClosestDiff = nDiff;
m_nClosestElement = oItem;
SizeCalculated(m_nClosestElement);
}
else
{
if(nDiff<nCurrentClosestDiff)
{
nCurrentClosestDiff = nDiff;
m_nClosestElement = oItem;
SizeCalculated(m_nClosestElement);
}
}
//-----get the new size
float fRelative = 1.0f - (float)nDiff/(float)m_nMaxDiff;
int nNewItemSize = m_nItemSizeMin + (int)(fRelative * m_nItemSizeDiff);
//-----set the new size
oItem.Resize(nNewItemSize, nNewItemSize);
oItem.SetIsInRange(true);
}
else
{
//----if the item is now out of range set it to the minimum size
if(oItem.GetIsInRange())
{
//-----set the minimum size
oItem.Resize(m_nItemSizeMin, m_nItemSizeMin);
oItem.SetIsInRange(false);
}
}
}
}
}
catch(Exception e)
{
Log.d("DeterminenSize", e.toString());
}
}
//****************************************************************************************************
// overrides
//****************************************************************************************************
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
super.onScrollChanged(l, t, oldl, oldt);
try
{
if(m_eMode == MODE.FINISH)
{
}
else
{
//------get the top element which must be a linear layout
if(m_oChild!=null)
{
m_oChild.setWillNotDraw(false);
FishEyeItem oFishEyeItem = null;
View oChildView = null;
ImageView oImage = null;
String cFilename = null;
int nPositionStart = 0;
int nPositionEnd = 0;
int nItemSize = 0;
int nScroll = getScrollX();
int nBoundEnd = getWidth();
int nItemPosition = 0;
int nCurrentClosestDiff = -1;
System.out.println(nScroll);
for(int i=0;i<m_oChild.getChildCount();i++)
{
oChildView = m_oChild.getChildAt(i);
//-----check if the child is of a certain type
if(oChildView instanceof FishEyeItem)
{
oFishEyeItem = (FishEyeItem)oChildView;
nItemSize = oFishEyeItem.getWidth();
nPositionStart = i * nItemSize;
nPositionEnd = nPositionStart + nItemSize;
oImage = oFishEyeItem.GetImageView();
cFilename = oFishEyeItem.GetImagePath();
//-----check if the item is in visible area
if(oImage!=null)
{
//-----image is in visible area
if(nPositionEnd>=nScroll - (m_nItemBuffer * nItemSize) && nPositionStart - (m_nItemBuffer * nScroll)<=nBoundEnd)
{
//-----check if image needs to be loaded
if(!oFishEyeItem.GetIsImageLoaded())
{
oFishEyeItem.SetIsImageLoaded(true);
new DownloadTaskImage(m_oActivity,
oImage,
cFilename,
nItemSize,
nItemSize,
m_fScaleFactor,
POWERROUNDMODES.ROUND).execute((Void)null);
}
//-----get the item position in the fisheyeview
nItemPosition = nPositionStart - nScroll + (int)(nItemSize*0.5f);
DeterminenSize(oFishEyeItem, Math.abs(m_nReferenceX - nItemPosition), nCurrentClosestDiff);
}
else
{
//-----check if an image can be recycle
if(oFishEyeItem.GetIsImageLoaded())
{
oFishEyeItem.SetIsImageLoaded(false);
new RecycleTaskImage(oImage).execute((Void)null);
}
}
}
}
}
}
}
}
catch(Exception e)
{
Log.d("onScrollChanged", e.toString());
}
}
@Override
public boolean onTouchEvent(MotionEvent oEvent)
{
super.onTouchEvent(oEvent);
try
{
switch(oEvent.getAction())
{
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_MOVE:
break;
default:
break;
}
}
catch(Exception e)
{
Log.d("onTouchEvent", e.toString());
}
return true;
}
protected void onFinishInflate()
{
super.onFinishInflate();
try
{
m_oChild = (LinearLayout)getChildAt(0);
}
catch(Exception e)
{
Log.d("onFinishInflate", e.toString());
}
}
}
未使用の変数のいくつかは気にしないでください。これらは、ビュー自体のスクロールが終了した後に自動スクロール機能を実現するために後で使用することを目的としているためです (スクロールビューを離すと、現在閉じているアイテムが常に真ん中にあるようにします)。 .
View は、実際に「FishEyeItem」で埋められる必要があります。これは、画像をロードしてコンテンツのサイズを変更するために使用されます。これらのアイテムは、表示する必要があるアイテムのリストを取得した後、実行時に読み込まれます。
FishEyeItem のコードは次のとおりです。
public class FishEyeItem extends RelativeLayout
{
//****************************************************************************************************
// variables
//****************************************************************************************************
//-----determines if this item can be resized
private boolean m_bCanBeResized = false;
//-----path to the image of this fisheye items image
private String m_cImagePath = null;
//-----determines if this item is in range for the fisheye calculation
private boolean m_bIsInRange = true;
//-----determines if the image is loaded already, thus occupying memory
private boolean m_bIsImageLoaded = false;
//-----id of the image4view holding the image
private int m_nImageViewID = -1;
//-----the id of the view in this view which is responsible for resizing
private int m_nResizeViewID = -1;
//****************************************************************************************************
// constructor
//****************************************************************************************************
public FishEyeItem(Context context)
{
super(context);
}
public FishEyeItem(Context context, AttributeSet attrs)
{
super(context, attrs);
}
//****************************************************************************************************
// setter
//****************************************************************************************************
public void SetCanBeResized(boolean bValue)
{
m_bCanBeResized = bValue;
}
public void SetImagePath(String cValue)
{
m_cImagePath = cValue;
}
public void SetIsInRange(boolean bValue)
{
m_bIsInRange = bValue;
}
public void SetIsImageLoaded(boolean bValue)
{
m_bIsImageLoaded = bValue;
}
public void SetImageViewID(int nValue)
{
m_nImageViewID = nValue;
}
public void SetResizeViewID(int nValue)
{
m_nResizeViewID = nValue;
}
//****************************************************************************************************
// getter
//****************************************************************************************************
public boolean GetCanBeResized()
{
return m_bCanBeResized;
}
public String GetImagePath()
{
return m_cImagePath;
}
public boolean GetIsInRange()
{
return m_bIsInRange;
}
public boolean GetIsImageLoaded()
{
return m_bIsImageLoaded;
}
public int GetImageViewID()
{
return m_nImageViewID;
}
public int GetResizeViewID()
{
return m_nResizeViewID;
}
public ImageView GetImageView()
{
ImageView oView = null;
try
{
oView = (ImageView)findViewById(m_nImageViewID);
}
catch(Exception e)
{
Log.d("GetImageView", e.toString());
}
return oView;
}
//****************************************************************************************************
// getter
//****************************************************************************************************
public void Resize(int nWidth, int nHeight)
{
try
{
View oView = findViewById(m_nResizeViewID);
if(oView!=null)
{
System.out.println("Resizing Item" + m_cImagePath);
//-----set the minimum size
RelativeLayout.LayoutParams oParams = (RelativeLayout.LayoutParams)oView.getLayoutParams();
oParams.width = nWidth;
oParams.height = nHeight;
oView.setLayoutParams(oParams);
}
}
catch(Exception e)
{
Log.d("Resize", e.toString());
}
}
}
したがって、基本的に onScrollChanged() が呼び出されるたびに、アイテムの画像がロードまたはリサイクルされます (どちらも非同期タスクで実行されているため、スクロールと GUI をブロックしません)。また、アイテムがスクロールビューの中央から一定の距離にある場合、アイテムのサイズが決定されます。
私が言ったように、Resize() メソッドは常に呼び出されます (それが system.out が存在する理由です) が、境界に対して「バウンス」すると、アイテムのサイズが変更されなくなります。
したがって、問題は HorizontalScrollView クラス自体のどこかにあると推測しています。たとえば、境界に対して「跳ねる」ときに特定のフラグが設定されます。
編集:
onscrollchanged() で getScrollX() をチェックし、その値が <= 0 の場合、または値が >= 最大境界の場合に返すだけで、アイテムが更新できなくなるのを防ぐことができました。ただし、これは、境界に対して「バウンス」したときにアイテムが更新されなかったという事実をまだ説明していません。