I am using this indexed_list_view extension to jump to a particular index in my finite ListView. This extension has limitations because of which if I scroll in the reverse direction, empty space is visible.
Limitation(as written on package readme): The list is always infinite both to positive and negative indexes. In other words, it can be scrolled indefinitely both to the top and to the bottom.
I want to disable reverse swipe when at first index in my ListView. I am using custom scroll physics to disable the left swipe, but I want to modify it so that the reverse scroll is disabled at top of the ListView, not always. Any help would be highly appreciable. Thanks.
import 'package:flutter/material.dart';
class CustomScrollPhysics extends ScrollPhysics {
CustomScrollPhysics({ScrollPhysics? parent}) : super(parent: parent);
bool isGoingLeft = false;
#override
ScrollPhysics applyTo(ScrollPhysics? ancestor) {
return CustomScrollPhysics(parent: buildParent(ancestor)!);
}
#override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
isGoingLeft = offset < 0;
return offset;
}
#override
double applyBoundaryConditions(ScrollMetrics position, double value) {
//print("applyBoundaryConditions");
assert(() {
if (value == position.pixels) {
throw FlutterError(
'$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
}
return true;
}());
if (value < position.pixels &&
position.pixels <= position.minScrollExtent) {
return value - position.pixels;
}
if (position.maxScrollExtent <= position.pixels &&
position.pixels < value) {
return value - position.pixels;
}
if (value < position.minScrollExtent &&
position.minScrollExtent < position.pixels) {
return value - position.minScrollExtent;
}
if (position.pixels < position.maxScrollExtent &&
position.maxScrollExtent < value) {
return value - position.maxScrollExtent;
}
if (!isGoingLeft) {
return value - position.pixels;
}
return 0.0;
}
}
I think if you just apply a controller to the ListView it should stop scrolling negatively.
Related
I have created a custom paint drawer, which works well, but I liked to create custom brush types like a calligraphy brush, crayon brush, etc...
class DrawingPainter extends CustomPainter {
DrawingPainter({this.pointsList}) : super();
List<DrawingPoints> pointsList;
#override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null) {
canvas.saveLayer(Offset.zero & size, Paint());
if (shouldDrawLine(i)) {
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points, pointsList[i].paint);
} else if (shouldDrawPoint(i)) {
canvas.drawPoints(dartUI.PointMode.polygon, [pointsList[i].points,pointsList[i].points], pointsList[i].paint);
}
canvas.restore();
}
}
}
bool shouldDrawPoint(int i) =>
pointsList[i] != null && pointsList.length > i + 1 && pointsList[i + 1] == null;
bool shouldDrawLine(int i) =>
pointsList[i] != null && pointsList.length > i + 1 && pointsList[i + 1] != null;
#override
bool shouldRepaint(DrawingPainter oldDelegate) => true;
}
this is my code for the drawing. How can I modify the brush drawing pattern like those types of brushes?
and today when I am developing my app, I just find out that I need a feature that can only allow user to scroll to the right page, but not the left page, and the PageController does not provide any functionality that can allow me to implement that, so that's the reason why I am here!
I also visited this link:
StackOverflow: Custom ScrollPhysics, but it does not contain any explanation, you know it's painful to use other people's code without knowing what it is doing, right? So please help me!!! ^_^
You can create your own ScrollPhysics to lock the scroll to a direction, or you can use the horizontal_blocked_scroll_physics lib to help you.
Here is an example of a CustomScrollPhysic that enable only right scrolling
class CustomScrollPhysics extends ScrollPhysics {
CustomScrollPhysics({ScrollPhysics parent}) : super(parent: parent);
bool isGoingLeft = false;
#override
CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
return CustomScrollPhysics(parent: buildParent(ancestor));
}
#override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
isGoingLeft = offset.sign < 0;
return offset;
}
#override
double applyBoundaryConditions(ScrollMetrics position, double value) {
assert(() {
if (value == position.pixels) {
throw FlutterError(
'$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
}
return true;
}());
if (value < position.pixels && position.pixels <= position.minScrollExtent)
return value - position.pixels;
if (position.maxScrollExtent <= position.pixels && position.pixels < value)
return value - position.pixels;
if (value < position.minScrollExtent &&
position.minScrollExtent < position.pixels)
return value - position.minScrollExtent;
if (position.pixels < position.maxScrollExtent &&
position.maxScrollExtent < value)
return value - position.maxScrollExtent;
if (!isGoingLeft) {
return value - position.pixels;
}
return 0.0;
}
}
You can use as the example below, CustomScrollPhysics as value to physics parameter.
PageView.builder(
physics: CustomScrollPhysics(),
);
Some references that could explain better how ScrollPhysics works
Flutter Doc
David Anaya's Medium article
I am using a list to store my items and changing the items indexes if swiped left or right but now when I am trying to swipe it does not stop I want the swipe to go from 1->2 or 2->3 or 3->2 but instead it goes 1->4 or 4->1.
This code below is the part of my program where I am swiping and calling the functions (where I increased the sensitivity to 15).
return GestureDetector(
onHorizontalDragUpdate: (dis) {
if (dis.delta.dx > 15) {
prevPage();
}
else if (dis.delta.dx < -15) { //User swiped from right to left
nextPage();
}
},
And these are the functions which it is calling.
void nextPage() {
if(currPage !=4) {
setState(() {
currPage += 1;
});
}
}
void prevPage() {
if(currPage != 1) {
setState(() {
currPage -= 1;
});
}
}
How can I fix this situation?
Thanks a lot.
After some tinkering and Googling all over the place I became super frustrated with the API / lack of documentation for ScrollPhysics.
On Android you can use what's called a SnapHelper inside your RecyclerView (analogous to a ListView in Flutter) that will automatically snap to a certain position.
The SnapHelper does this on a position based API.
You can ask which View is currently in your chosen ViewPort and get its position and ask the RecyclerView to animate to that position.
Flutter on the other hand wants us to work with logical pixels, which makes this super trivial, common pattern difficult to implement.
All the solutions I found was to use items inside the list that have a fixed width/height and don't account for flinging gestures.
tl;dr How to implement this ๐in Flutter so it works for any item in a ListView?
I'm including the poor mans version we are currently using.
Which works, but not like we are used to on Android.
Especially passing the itemWidth is an eyesore
class SnappingListScrollPhysics extends ScrollPhysics {
final double itemWidth;
const SnappingListScrollPhysics({
#required this.itemWidth,
ScrollPhysics parent,
}) : super(parent: parent);
#override
SnappingListScrollPhysics applyTo(ScrollPhysics ancestor) => SnappingListScrollPhysics(
parent: buildParent(ancestor),
itemWidth: itemWidth,
);
double _getItem(ScrollPosition position) => (position.pixels) / itemWidth;
double _getPixels(ScrollPosition position, double item) => min(item * itemWidth, position.maxScrollExtent);
double _getTargetPixels(ScrollPosition position, Tolerance tolerance, double velocity) {
double item = _getItem(position);
if (velocity < -tolerance.velocity) {
item -= 0.5;
} else if (velocity > tolerance.velocity) {
item += 0.5;
}
return _getPixels(position, item.roundToDouble());
}
#override
Simulation createBallisticSimulation(ScrollMetrics position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
return super.createBallisticSimulation(position, velocity);
}
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels) {
return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
}
return null;
}
#override
bool get allowImplicitScrolling => false;
}
I am making a game where the floor consists of individual live and dead pixels. If a line of live pixels are drawn between the two points, you pass. If the line is broken, you cannot pass.
I would like to detect wether all objects between two points are of the same Tag. Here is a drawing to try and illustrate this:
At the moment, I have the following code that checks if the next "pixel" is live or dead using RayCasts:
function Update () {
var pixfwd = transform.TransformDirection (Vector3.up * Reach);
var pixbwd = transform.TransformDirection (Vector3.down * Reach);
var pixleft = transform.TransformDirection (Vector3.left * Reach);
var pixright = transform.TransformDirection (Vector3.right * Reach);
Debug.DrawRay(transform.position, pixfwd * Reach, Color.red, 0.1f);
Debug.DrawRay(transform.position, pixbwd * Reach, Color.green, 0.1f);
Debug.DrawRay(transform.position, pixleft * Reach, Color.yellow, 0.1f);
Debug.DrawRay(transform.position, pixright * Reach, Color.blue, 0.1f);
Physics.Raycast (transform.position, pixfwd, pixhit);
Physics.Raycast (transform.position, pixbwd, pixhit2);
Physics.Raycast (transform.position, pixleft, pixhit3);
Physics.Raycast (transform.position, pixright, pixhit4);
if ( checkVision(pixhit) || checkVision(pixhit2) || checkVision(pixhit3) || checkVision(pixhit4) ) {
nextisLive = true;
}
else
{
nextisLive=false;
}
}
function checkVision(pixhit:RaycastHit):boolean
{
if ( pixhit != null && pixhit.collider != null && pixhit.collider.tag == "Live" )
{
return true;
}
return false;
if ( pixhit2 != null && pixhit2.collider != null && pixhit2.collider.tag == "Live" )
{
return true;
}
return false;
if ( pixhit3 != null && pixhit3.collider != null && pixhit3.collider.tag == "Live" )
{
return true;
}
return false;
if ( pixhit4 != null && pixhit4.collider != null && pixhit4.collider.tag == "Live" )
{
return true;
}
return false;
}
An approach to this problem which doesn't rely on Unity's physics system is to store your "pixel" objects in a 2D array, and iterate through the array to evaluate whether or not the live pixels form a continuous path from one side to the other.
Note: This assumes that when you create/initialize your game, you store your pixel objects in the array correctly, reflective of their in-game arrangement. (ie. Representative of the rows and columns of the grid they form.)
Here's an idea of how your path validation algorithm might look:
var pixels : GameObject[,];
function Start()
{
// Populate pixels array in here, or when you create the grid if the pixels are
// created dynamically.
//
// Array should be initialized to new GameObject[GRID_WIDTH, GRID_HEIGHT];
//
// For this approach, it can be helpful if you group your pixel GameObjects under
// empty GameObjects that match the structure of the array, as this will make it
// easier to populate.
// (Only really applies if you the pixels are not created dynamically.)
}
// Accepts two parameters, which provide the top and bottom of the generator at each
// point.
//
// (For example, a generator spanning between pixels 0 and 2 in width would be
// represented by new int[]{0, 2})
function CheckForClearPath(startPoint : int[], endPoint : int[])
{
// For tracking live pixels in last and current column
var prevColumn : boolean[] = new boolean[pixels[0].length];
var currColumn : boolean[] = new boolean[pixels[0].length];
InitializeArray(prevColumn);
// Iterating through each column of grid
var x : int = 0;
for (x = 0; x < pixels.length; x++)
{
// Special cases for first and last column
var isFirstColumn : boolean = (x == 0);
var isLastColumn : boolean = (x == pixels.length - 1);
// Iterating through each row of grid column, first to identify live pixels
// adjacent to previous column's live pixels
var y : int = 0;
for (y = 0; y < pixels[x].length; y++)
{
if (prevColumn[x]) {
currColumn[y] = (pixels[x][y].tag == "Live");
}
else {
currColumn[y] = false;
}
}
// Next, iterating through each row of grid column, to identify live pixels
// adjacent to current column's live pixels
//
// This is done by checking whether current pixel is live, then checking whether
// next pixel has live tag
for (y = 0; y < pixels[x].length - 1; y++)
{
if (currColumn[y]){
currColumn[y + 1] = (pixels[x][y].tag == "Live");
}
}
// Check if any pixels are recorded as alive in column - if not, it means that no
// live pixels were adjacent to last columns live pixels, and path is broken.
if (AreAllPixelsDead(currColumn)) {
return false;
}
// If first column, check if pixels next to start point are live.
if (isFirstColumn) {
if (!DoesPathMeetPoint(startPoint, currColumn)) {
return false;
}
}
// If last column, check if pixels next to end point are live.
if (isLastColumn) {
if (!DoesPathMeetPoint(endPoint, currColumn)) {
return false;
}
}
// Saving current column results in last column results
for (x = 0; x < pixels.length; x++)
{
prevColumn[x] = currColumn[x];
}
}
// If all goes well, path is valid
return true;
}
function InitializeArray(arrayRef : boolean[]) {
for (var i : int = 0; i < arrayRef.length; i++)
{
arrayRef[i] = true;
}
}
function AreAllPixelsDead(arrayRef : boolean[]) {
for (var i : int = 0; i < arrayRef.length; i++)
{
if (arrayRef[i]) {
return false;
}
}
return true;
}
function DoesPathMeetPoint(point : int[], columnPixels : boolean[]) {
for (var i : int = 0; i < columnPixels.length; i++)
{
if (columnPixels[i] && i >= point[0] && i <= point[1]) {
return true;
}
}
return false;
}
Basically, the algorithm goes through each column of the grid, and determines whether there are live pixels adjacent to the previous column's live pixels, and live pixels adjacent to those. Successfully passing this test means that the live pixels in the grid form at least one continuous path from one end to the other. (Then there are the couple special checks to make sure the path connects to the start and end points.)
Hope this helps! Let me know if you have any questions.
Disclaimer: Code not tested, but the logic of the algorithm is there.