我想要得到的是相同的行为,这卷轴观点有:
我知道,这是使用HTML,而不是原生API,但我想实现它作为一个UIKit的组成部分。
现在,我在寻找的行为:
- 请注意,这是一个分页滚动视图,但“页面大小”小于视图的宽度。
- 当你把它的滚动由左到右各页“嵌入”到最左边的项目。
- 当您从右端向左它“捕捉”到最右边的项滚动它。
在同一个页面,但现在从右到左:
我已经试过:
- 我试图使滚动视图小于它的超视图和压倒一切的则hitTest,这让我留下对正确的行为。
- 我已经试过落实scrollViewWillEndDragging:withVelocity:targetContentOffset:和设置targetContentOffset我想,但因为我不能改变它只是滚动太慢或太快的速度。
- 我已经试过落实scrollViewDidEndDecelerating:然后动画到正确的偏移,但滚动视图首先停止然后移动时,它看起来不自然。
- 我已经试过落实scrollViewDidEndDragging:willDecelerate:然后动画到正确的偏移,但滚动视图“跳”,不正确动画。
我的想法。
谢谢!
更新:
最后我用罗布Mayoff的方法,它看起来干净。 我改变了它,所以当速度为0,例如,当用户拖拽,停止,松开手指会工作。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(CGPoint *)targetContentOffset {
CGFloat maxOffset = scrollView.contentSize.width - scrollView.bounds.size.width;
CGFloat minOffset = 0;
if (velocity.x == 0) {
CGFloat targetX = MAX(minOffset,MIN(maxOffset, targetContentOffset->x));
CGFloat diff = targetX - baseOffset;
if (ABS(diff) > offsetStep/2) {
if (diff > 0) {
//going left
baseOffset = MIN(maxOffset, baseOffset + offsetStep);
} else {
//going right
baseOffset = MAX(minOffset, baseOffset - offsetStep);
}
}
} else {
if (velocity.x > 0) {
baseOffset = MIN(maxOffset, baseOffset + offsetStep);
} else {
baseOffset = MAX(minOffset, baseOffset - offsetStep);
}
}
targetContentOffset->x = baseOffset;
}
这种解决方案的唯一问题是,刷卡滚动视图不产生“反弹”的效果。 感觉“卡住”。
设置scrollView.decelerationRate = UIScrollViewDecelerationRateFast
,与实施相结合scrollViewWillEndDragging:withVelocity:targetContentOffset:
在我看来,使用集合视图的工作。
首先,我给自己一些实例变量:
@implementation ViewController {
NSString *cellClassName;
CGFloat baseOffset;
CGFloat offsetStep;
}
在viewDidLoad
,我设置视图的decelerationRate
:
- (void)viewDidLoad {
[super viewDidLoad];
cellClassName = NSStringFromClass([MyCell class]);
[self.collectionView registerNib:[UINib nibWithNibName:cellClassName bundle:nil] forCellWithReuseIdentifier:cellClassName];
self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast;
}
我需要offsetStep
是适合在视图的屏幕上的界限项目的整数倍的大小。 我计算它在viewDidLayoutSubviews
:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
CGFloat stepUnit = layout.itemSize.width + layout.minimumLineSpacing;
offsetStep = stepUnit * floorf(self.collectionView.bounds.size.width / stepUnit);
}
我需要baseOffset
到可以在X视图的滚动开始之前所抵消。 我初始化它在viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
baseOffset = self.collectionView.contentOffset.x;
}
然后,我需要强制视图中的步骤进行滚动offsetStep
。 我这样做,在scrollViewWillEndDragging:withVelocity:targetContentOffset:
根据不同的velocity
,我增加或减少baseOffset
通过offsetStep
。 但我钳baseOffset
的最小值为0,最大的的contentSize.width - bounds.size.width
。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
if (velocity.x < 0) {
baseOffset = MAX(0, baseOffset - offsetStep);
} else if (velocity.x > 0) {
baseOffset = MIN(scrollView.contentSize.width - scrollView.bounds.size.width, baseOffset + offsetStep);
}
targetContentOffset->x = baseOffset;
}
请注意,我不在乎什么targetContentOffset->x
进来的。
这具有使位于最左侧可视项的左边缘,直到用户滚动一路到最后一个项目的效果。 在这一点上它对齐到最右边的可见项目的右边缘,直到用户滚动一路到左侧。 这似乎符合App Store应用的行为。
如果不适合你,你可以尝试将最后一行( targetContentOffset->x = baseOffset
本):
dispatch_async(dispatch_get_main_queue(), ^{
[scrollView setContentOffset:CGPointMake(baseOffset, 0) animated:YES];
});
这也为我工作。
你可以找到我的测试应用程序在此git仓库 。
你可以打开pagingEnabled
,或使用decelerationRate = UIScrollViewDecelerationRateFast
联合scrollViewDidEndDragging
和scrollViewDidEndDecelerating
(这将最小化减慢滚动到一个位置,然后再次动画到另一个位置的效果)。 这可能不正是你想要的,但它是相当接近。 通过使用animateWithDuration
它避免了最终捕捉的瞬间跳跃。
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate)
[self snapScrollView:scrollView];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self snapScrollView:scrollView];
}
然后,你可以写一个snapScrollView
,如:
- (void)snapScrollView:(UIScrollView *)scrollView
{
CGPoint offset = scrollView.contentOffset;
if ((offset.x + scrollView.frame.size.width) >= scrollView.contentSize.width)
{
// no snap needed ... we're at the end of the scrollview
return;
}
// calculate where you want it to snap to
offset.x = floorf(offset.x / kIconOffset + 0.5) * kIconOffset;
// now snap it to there
[UIView animateWithDuration:0.1
animations:^{
scrollView.contentOffset = offset;
}];
}
简单的解决方案,原理类似于App Store的,具有速度或没有它。 kCellBaseWidth
-细胞的宽度。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset
{
NSInteger index = lrint(targetContentOffset->x/kCellBaseWidth);
targetContentOffset->x = index * kCellBaseWidth;
}
插嘴的斯威夫特。 @avdyushin的答案是目前为止最简单,正如本所提到的,效果非常好。 虽然我从没有添加一块@Rob的回答关于滚动视图的结尾。 总之,这个解决方案似乎很好地工作。
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if ((scrollView.contentOffset.x + scrollView.frame.size.width) >= scrollView.contentSize.width) {
// no snap needed ... we're at the end of the scrollview
return
}
let index: CGFloat = CGFloat(lrintf(Float(targetContentOffset.memory.x) / kCellBaseWidth))
targetContentOffset.memory.x = index * kCellBaseWidth
}
只要你加入minimumLineSpacing
到kCellBaseWidth
,瞧。