所以我现在面临的问题如下,我想创建一个鱼眼视图(所以一个包含项目的视图,而中间的项目比你在 MAC OS 项目栏或其他地方看到的其他项目更大)。
到目前为止,我已经扩展了 HorizontalScrollView 来实现这一点,经过一些测试,一切似乎都很好,所以在移动滚动视图时,项目会根据它们的位置正确更新。
但是,如果滚动视图“反弹”其边界,则会出现一个问题。因此,如果 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());
}
}
}
不要介意一些未使用的变量,因为一旦视图本身完成滚动,它们将在以后用于实现自动滚动功能(这样当前关闭的项目将始终在放开滚动视图的中间) .
视图需要实际填充“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 或该值是否 >= 最大边界来防止项目无法再更新。然而,这仍然不能解释项目在“反弹”边界时不再更新的事实。