I have created a CarouselSlider trying to make it work as expected but only images changing, not the dots indicator or the titles :
here are my code :
The screen
class OnBoarding extends StatefulWidget {
static var tag = "/ShWalkThroughScreen";
#override
_OnBoardingState createState() => _OnBoardingState();
}
class _OnBoardingState extends State<OnBoarding> {
OnBoardingController _onBoardingController = Get.put(OnBoardingController());
#override
void dispose() {
super.dispose();
changeStatusColor(Colors.white);
}
#override
Widget build(BuildContext context) {
changeStatusColor(Colors.white);
var width = MediaQuery.of(context).size.width;
width = width - 50;
_onBoardingController.gettingWalkThroughData();
return Obx(()=> Scaffold(
body: SafeArea(
child: Container(
height: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(spacing_large, spacing_large, spacing_large, spacing_standard_new),
child: Column(
children: <Widget>[
text(_onBoardingController.mHeadingList[_onBoardingController.position.value], textColor: sh_textColorPrimary, fontSize: textSizeLarge, fontFamily: fontBold),
SizedBox(height: 10.0),
text(_onBoardingController.mSubtitle1ingList[_onBoardingController.position.value], fontSize: textSizeLargeMedium, maxLine: 3, isCentered: true),
],
),
),
ShCarouselSlider(
viewportFraction: 0.8,
height: MediaQuery.of(context).size.height * 0.5,
enlargeCenterPage: true,
scrollDirection: Axis.horizontal,
items: _onBoardingController.mSliderList.map((slider) {
return Builder(
builder: (BuildContext context) {
return Container(
width: width * 0.9,
//height: width + width * 0.1,
decoration: BoxDecoration(
color: sh_white,
borderRadius: BorderRadius.all(Radius.circular(spacing_standard)),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.4), spreadRadius: spacing_control_half, blurRadius: 10, offset: Offset(1, 3))],
),
margin: EdgeInsets.all(spacing_standard_new),
child: Center(
child: CachedNetworkImage(
placeholder: placeholderWidgetFn() as Widget Function(BuildContext, String), imageUrl: slider, width: MediaQuery.of(context).size.width, fit: BoxFit.cover),
),
);
},
);
}).toList(),
onPageChanged: (index) {
_onBoardingController.changeIndex(index);
},
),
Padding(
padding: const EdgeInsets.all(spacing_large),
child: Column(
children: <Widget>[
Obx(()=> DotsIndicator(
dotsCount: 3,
position: _onBoardingController.position.value,
decorator: DotsDecorator(color: sh_view_color, activeColor: sh_colorPrimary, activeSize: const Size.square(14.0), spacing: EdgeInsets.all(spacing_control)),
),
),
SizedBox(height: spacing_standard),
SizedBox(
width: double.infinity,
height: 50,
child: MaterialButton(
padding: EdgeInsets.all(spacing_standard),
child: Text(sh_text_start_to_shopping, style: TextStyle(fontSize: 18)),
textColor: sh_white,
shape: RoundedRectangleBorder(borderRadius: new BorderRadius.circular(40.0)),
color: sh_colorPrimary,
onPressed: () {
finish(context);
ShHomeScreen().launch(context);
},
),
),
SizedBox(height: spacing_standard),
InkWell(
onTap: () {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) {
return T3SignIn();
}));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
text(sh_lbl_already_have_a_account),
text(sh_lbl_sign_in, textColor: sh_textColorPrimary, fontFamily: fontBold),
],
),
)
],
),
),
],
),
),
),
),
),
);
}
}
// ignore: must_be_immutable
class ShSliderWidget extends StatelessWidget {
OnBoardingController _onBoardingController = Get.put(OnBoardingController());
#override
Widget build(BuildContext context) {
var width = MediaQuery.of(context).size.width;
width = width - 50;
final Size cardSize = Size(width, width / 1.8);
return ShCarouselSlider(
viewportFraction: 0.9,
height: cardSize.height,
enlargeCenterPage: true,
scrollDirection: Axis.horizontal,
items: _onBoardingController.mSliderList.map((slider) {
return Builder(
builder: (BuildContext context) {
return Obx(()=> Container(
width: MediaQuery.of(context).size.width,
height: cardSize.height,
margin: EdgeInsets.symmetric(horizontal: 8.0),
child: Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
elevation: 0,
margin: EdgeInsets.all(0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
child: CachedNetworkImage(
placeholder: placeholderWidgetFn() as Widget Function(BuildContext, String),
imageUrl: slider,
fit: BoxFit.fill,
width: MediaQuery.of(context).size.width,
height: cardSize.height),
),
),
);
},
);
}).toList(),
onPageChanged: (index) {
_onBoardingController.position.value = index;
},
);
}
}
The Widget
class ShCarouselSlider extends StatefulWidget {
ShCarouselSlider(
{#required List<Widget> this.items,
this.height,
this.aspectRatio: 16 / 9,
this.viewportFraction: 0.8,
this.initialPage: 0,
int realPage: 10000,
this.enableInfiniteScroll: true,
this.reverse: false,
this.autoPlay: false,
this.autoPlayInterval: const Duration(seconds: 4),
this.autoPlayAnimationDuration = const Duration(milliseconds: 800),
this.autoPlayCurve: Curves.fastOutSlowIn,
this.pauseAutoPlayOnTouch,
this.enlargeCenterPage = false,
this.onPageChanged,
this.scrollPhysics,
this.scrollDirection: Axis.horizontal})
: this.realPage = enableInfiniteScroll ? realPage + initialPage : initialPage,
this.itemCount = items.length,
this.itemBuilder = null,
this.pageController = PageController(
viewportFraction: viewportFraction as double,
initialPage: enableInfiniteScroll ? realPage + (initialPage as int) : initialPage as int,
);
/// The on demand item builder constructor
ShCarouselSlider.builder(
{#required this.itemCount,
#required this.itemBuilder,
this.height,
this.aspectRatio: 16 / 9,
this.viewportFraction: 0.8,
this.initialPage: 0,
int realPage: 10000,
this.enableInfiniteScroll: true,
this.reverse: false,
this.autoPlay: false,
this.autoPlayInterval: const Duration(seconds: 4),
this.autoPlayAnimationDuration = const Duration(milliseconds: 800),
this.autoPlayCurve: Curves.fastOutSlowIn,
this.pauseAutoPlayOnTouch,
this.enlargeCenterPage = false,
this.onPageChanged,
this.scrollPhysics,
this.scrollDirection: Axis.horizontal})
: this.realPage = enableInfiniteScroll ? realPage + initialPage : initialPage,
this.items = null,
this.pageController = PageController(
viewportFraction: viewportFraction as double,
initialPage: enableInfiniteScroll ? realPage + (initialPage as int) : initialPage as int,
);
/// The widgets to be shown in the carousel of default constructor
final List<Widget> items;
/// The widget item builder that will be used to build item on demand
final IndexedWidgetBuilder itemBuilder;
/// The widgets count that should be shown at carousel
final int itemCount;
/// Set carousel height and overrides any existing [aspectRatio].
final double height;
/// Aspect ratio is used if no height have been declared.
///
/// Defaults to 16:9 aspect ratio.
final double aspectRatio;
/// The fraction of the viewport that each page should occupy.
///
/// Defaults to 0.8, which means each page fills 80% of the carousel.
final num viewportFraction;
/// The initial page to show when first creating the [ShCarouselSlider].
///
/// Defaults to 0.
final num initialPage;
/// The actual index of the [PageView].
///
/// This value can be ignored unless you know the carousel will be scrolled
/// backwards more then 10000 pages.
/// Defaults to 10000 to simulate infinite backwards scrolling.
final num realPage;
///Determines if carousel should loop infinitely or be limited to item length.
///
///Defaults to true, i.e. infinite loop.
final bool enableInfiniteScroll;
/// Reverse the order of items if set to true.
///
/// Defaults to false.
final bool reverse;
/// Enables auto play, sliding one page at a time.
///
/// Use [autoPlayInterval] to determent the frequency of slides.
/// Defaults to false.
final bool autoPlay;
/// Sets Duration to determent the frequency of slides when
///
/// [autoPlay] is set to true.
/// Defaults to 4 seconds.
final Duration autoPlayInterval;
/// The animation duration between two transitioning pages while in auto playback.
///
/// Defaults to 800 ms.
final Duration autoPlayAnimationDuration;
/// Determines the animation curve physics.
///
/// Defaults to [Curves.fastOutSlowIn].
final Curve autoPlayCurve;
/// Sets a timer on touch detected that pause the auto play with
/// the given [Duration].
///
/// Touch Detection is only active if [autoPlay] is true.
final Duration pauseAutoPlayOnTouch;
/// Determines if current page should be larger then the side images,
/// creating a feeling of depth in the carousel.
///
/// Defaults to false.
final bool enlargeCenterPage;
/// The axis along which the page view scrolls.
///
/// Defaults to [Axis.horizontal].
final Axis scrollDirection;
/// Called whenever the page in the center of the viewport changes.
final Function(int index) onPageChanged;
/// How the carousel should respond to user input.
///
/// For example, determines how the items continues to animate after the
/// user stops dragging the page view.
///
/// The physics are modified to snap to page boundaries using
/// [PageScrollPhysics] prior to being used.
///
/// Defaults to matching platform conventions.
final ScrollPhysics scrollPhysics;
/// [pageController] is created using the properties passed to the constructor
/// and can be used to control the [PageView] it is passed to.
final PageController pageController;
/// Animates the controlled [ShCarouselSlider] to the next page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> nextPage({#required Duration duration, #required Curve curve}) {
return pageController.nextPage(duration: duration, curve: curve);
}
/// Animates the controlled [ShCarouselSlider] to the previous page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> previousPage({#required Duration duration, #required Curve curve}) {
return pageController.previousPage(duration: duration, curve: curve);
}
/// Changes which page is displayed in the controlled [ShCarouselSlider].
///
/// Jumps the page position from its current value to the given value,
/// without animation, and without checking if the new value is in range.
void jumpToPage(int page) {
final index = _getRealIndex(pageController.page.toInt(), realPage - initialPage as int, itemCount);
return pageController.jumpToPage(pageController.page.toInt() + page - index);
}
/// Animates the controlled [ShCarouselSlider] from the current page to the given page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> animateToPage(int page, {#required Duration duration, #required Curve curve}) {
final index = _getRealIndex(pageController.page.toInt(), realPage - initialPage as int, itemCount);
return pageController.animateToPage(pageController.page.toInt() + page - index, duration: duration, curve: curve);
}
#override
_ShCarouselSliderState createState() => _ShCarouselSliderState();
}
class _ShCarouselSliderState extends State<ShCarouselSlider> with TickerProviderStateMixin {
Timer timer;
#override
void initState() {
super.initState();
timer = getTimer();
}
Timer getTimer() {
return widget.autoPlay
? Timer.periodic(widget.autoPlayInterval, (_) {
widget.pageController.nextPage(duration: widget.autoPlayAnimationDuration, curve: widget.autoPlayCurve);
})
: null;
}
void pauseOnTouch() {
timer.cancel();
timer = Timer(widget.pauseAutoPlayOnTouch, () {
timer = getTimer();
});
}
Widget getWrapper(Widget child) {
if (widget.height != null) {
final Widget wrapper = Container(height: widget.height, child: child);
return widget.autoPlay && widget.pauseAutoPlayOnTouch != null ? addGestureDetection(wrapper) : wrapper;
} else {
final Widget wrapper = AspectRatio(aspectRatio: widget.aspectRatio, child: child);
return widget.autoPlay && widget.pauseAutoPlayOnTouch != null ? addGestureDetection(wrapper) : wrapper;
}
}
Widget addGestureDetection(Widget child) => GestureDetector(onPanDown: (_) => pauseOnTouch(), child: child);
#override
void dispose() {
super.dispose();
timer?.cancel();
}
#override
Widget build(BuildContext context) {
return getWrapper(CarouselSlider(
options: CarouselOptions(
enlargeCenterPage: true,
viewportFraction: 0.8,
),
items: widget.items.map((i) {
return Container(
child: i,
);
}).toList(),
));
}
}
/// Converts an index of a set size to the corresponding index of a collection of another size
/// as if they were circular.
///
/// Takes a [position] from collection Foo, a [base] from where Foo's index originated
/// and the [length] of a second collection Baa, for which the correlating index is sought.
///
/// For example; We have a Carousel of 10000(simulating infinity) but only 6 images.
/// We need to repeat the images to give the illusion of a never ending stream.
/// By calling _getRealIndex with position and base we get an offset.
/// This offset modulo our length, 6, will return a number between 0 and 5, which represent the image
/// to be placed in the given position.
int _getRealIndex(int position, int base, int length) {
final int offset = position - base;
return _remainder(offset, length);
}
/// Returns the remainder of the modulo operation [input] % [source], and adjust it for
/// negative values.
int _remainder(int input, int source) {
if (source == 0) return 0;
final int result = input % source;
return result < 0 ? source + result : result;
}
The Controller
class OnBoardingController extends GetxController {
var onBoardingLoading = false.obs;
var position = 0.obs;
var mSliderList = <String>[].obs;
var mHeadingList = <String>["Hi, Welcome", "Most Unique Styles!", "Shop Till You Drop!"].obs;
var mSubtitle1ingList = <String>[
"We make around your city Affordable,easy and efficient.",
"Shop the most trending fashion on the biggest shopping website",
"Grab the best seller pieces at bargain prices."
];
gettingWalkThroughData() {
onBoardingLoading.value = true;
ApiServices().getWalkTrough().then((resp) {
onBoardingLoading.value = false;
mSliderList.clear();
mHeadingList.clear();
mSubtitle1ingList.clear();
for (var item in resp['data']){
mSliderList.add("${Constants.IMAGE_BASE_URL}${item['image']}/");
mHeadingList.add(item['title']);
mSubtitle1ingList.add(item['sub_title']);
}
});
}
changeIndex(int index){
position.value = index;
}
}
Related
I am building a simple calculator and using Riverpod for state management. Though I can update state, the UI is not being updated with the changes... Can someone tell me what I'm doing wrong ?? Here's the code:
Calculator Model
class CalculatorModel {
final bool shouldAppend;
final String equation;
final String result;
const CalculatorModel(
{this.shouldAppend = true, this.equation = '0', this.result = '0'});
CalculatorModel copyWith({
bool? shouldAppend,
String? equation,
String? result,
}) =>
CalculatorModel(
shouldAppend: shouldAppend ?? this.shouldAppend,
equation: equation ?? this.equation,
result: result ?? this.result);
}
Calculator State Notifier Implementation
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:components/calculator_components/calculator_model.dart';
import 'package:math_expressions/math_expressions.dart';
final calculatorProvider =
StateNotifierProvider<CalculatorStateNotifier, List<CalculatorModel>>(
(ref) => CalculatorStateNotifier());
class CalculatorStateNotifier extends StateNotifier<List<CalculatorModel>> {
CalculatorStateNotifier() : super([const CalculatorModel()]);
void append(String calcInput) {
final equation = () {
return state[0].equation == '0'
? calcInput
: state[0].equation + calcInput;
}();
state[0] = CalculatorModel(equation: equation);
}
}
Click function for calculator buttons. State is getting updated, successfully...
void onClickedButton(String calcInput, WidgetRef ref) {
ref.read(calculatorProvider.notifier).append(calcInput);
ref.watch(calculatorProvider);
print(ref.watch(calculatorProvider)[0].equation);
}
Riverpod not updating UI when called in the presentation layer...
#override
Widget build(BuildContext context, WidgetRef ref) {
Size size = MediaQuery.of(context).size;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
margin: const EdgeInsets.symmetric(vertical: 10),
width: size.width * 0.8,
child: Column(children: [
Expanded(
child: Container(
child: Padding(
padding: const EdgeInsets.only(top: 15.0, right: 22),
child: Consumer(builder: (context, ref, _) {
return buildCalculatorScreen(
ref.watch(calculatorProvider)[0].equation,
ref.watch(calculatorProvider)[0].result);
}),
)),
),
]),
);
}
}
First, you should not use ref.watch on asynchronous calls, including button calls.
Second, Since our state is immutable, we are not allowed to do state[0] = . You need to update your List in some other way, such as using the spread operator or List.of()
More information here:
StateNotifierProvider from Riverpod
state should be immutable, you have to set a new object/array as the new state.
You can do something like this:
final newState = List.from(state);
newState[0] = CalculatorModel(equation: equation);
state = newState;
I want to show the all images from the API in the carousel and for items, I want to show 2 items per slide.
and it works correctly what I want. but in every last item, it shows me the error of CarouselSlider. I don't know why it gives me this error. I tried to solve it but failed because I don't know why they give me this error in the last item.
here is my code:-
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
class PersonImages extends StatefulWidget {
PersonImages({Key? key}) : super(key: key);
#override
_PersonImages createState() => _PersonImages();
}
class _PersonImages extends State<PersonImages>{
var UsriD = Auth.prefs?.getString('usrid');
var Imagedata;
var img = "";
var user = "";
#override
void initState() {
super.initState();
getImageData();
}
getImageData() async{
var res = await http.get(Uri.https('www.*******.net', '/index.php',{'act':'usrPhotos','Usrid': '${UsriD}'}));
Imagedata = jsonDecode(res.body);
//print(Imagedata);
setState(() {});
print(res.body);
}
#override
Widget build(BuildContext context) {
//print(Imagedata);
return
Imagedata != null? CarouselSlider.builder(
options: CarouselOptions(
//height: 150,
aspectRatio: 2.0,
enableInfiniteScroll: false,
//viewportFraction: 0.8,
enlargeCenterPage: true,
viewportFraction: 1,
),
itemCount: (Imagedata.length / 2).round(),
//itemCount: 3,
itemBuilder: (BuildContext context, int index, int pageViewIndex) {
final int first = index * 2;
final int second = first + 1;
return
Row(
children: [first, second].map((idx) {
return Expanded(
//flex: 2,
child: Container(
child: Container(
margin: EdgeInsets.all(5.0),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
child:
Image.network(
"https://www.*******.net/files/images/${Imagedata[idx]['image']}",
fit: BoxFit.cover,
width: 200,
height: 200,
),
),
),
)
);
}).toList(),
);
}
): const Center(
child: CircularProgressIndicator(),
);
}
}
Here is my error and output :-
please help me if anyone knows how to resolve it.
is anyone knows how to do this so answer my question?
In case of an odd number length, for example 5
you divided it by 2. which is 2.5 Rounded it then the value is 3.
Now when it loops for the third time. It will try and fetch 2*2 + 1. now you will try and find the element by id 5 which doesn't exist. You can try
itemCount: (Imagedata.length / 2).floor(),
Note you may lose the last image in case of an odd length.
Try checking null like
Imagedata[idx]['image']!= null?
Image.network("...${Imagedata[idx]['image']}", )
:Text("cant find image index"),
I am building a custom TextSelectionControls with an extends from MaterialTextSelectionControls.
How do I verticalize the Text Selection Toolbar in TextField?
It's like this now:
And I want it to be like this so I can add more custom options:
This is the part that builds the toolbar:
return TextSelectionToolbar(
anchorAbove: anchorAbove,
anchorBelow: anchorBelow,
children: [
...itemDatas
.asMap()
.entries
.map((MapEntry<int, _TextSelectionToolbarItemData> entry) {
return TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(
entry.key, itemDatas.length),
onPressed: entry.value.onPressed,
child: Text(entry.value.label),
);
}),
],
);
And this is the complete code:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class CustomTextSelectionControls extends MaterialTextSelectionControls {
/// Builder for material-style copy/paste text selection toolbar.
#override
Widget buildToolbar(
BuildContext context,
Rect globalEditableRegion,
double textLineHeight,
Offset selectionMidpoint,
List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate,
ClipboardStatusNotifier? clipboardStatus,
Offset? lastSecondaryTapDownPosition,
) {
return _TextSelectionControlsToolbar(
globalEditableRegion: globalEditableRegion,
textLineHeight: textLineHeight,
selectionMidpoint: selectionMidpoint,
endpoints: endpoints,
delegate: delegate,
clipboardStatus: clipboardStatus,
handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null,
handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
handleSelectAll:
canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
);
}
}
class _TextSelectionControlsToolbar extends StatefulWidget {
const _TextSelectionControlsToolbar({
required this.clipboardStatus,
required this.delegate,
required this.endpoints,
required this.globalEditableRegion,
required this.handleCut,
required this.handleCopy,
required this.handlePaste,
required this.handleSelectAll,
required this.selectionMidpoint,
required this.textLineHeight,
});
final ClipboardStatusNotifier? clipboardStatus;
final TextSelectionDelegate delegate;
final List<TextSelectionPoint> endpoints;
final Rect globalEditableRegion;
final VoidCallback? handleCut;
final VoidCallback? handleCopy;
final VoidCallback? handlePaste;
final VoidCallback? handleSelectAll;
final Offset selectionMidpoint;
final double textLineHeight;
#override
_TextSelectionControlsToolbarState createState() =>
_TextSelectionControlsToolbarState();
}
class _TextSelectionControlsToolbarState
extends State<_TextSelectionControlsToolbar> with TickerProviderStateMixin {
// Padding between the toolbar and the anchor.
static const double _kToolbarContentDistanceBelow = 20.0;
static const double _kToolbarContentDistance = 8.0;
void _onChangedClipboardStatus() {
setState(() {
// Inform the widget that the value of clipboardStatus has changed.
});
}
#override
void initState() {
super.initState();
widget.clipboardStatus?.addListener(_onChangedClipboardStatus);
}
#override
void didUpdateWidget(_TextSelectionControlsToolbar oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.clipboardStatus != oldWidget.clipboardStatus) {
widget.clipboardStatus?.addListener(_onChangedClipboardStatus);
oldWidget.clipboardStatus?.removeListener(_onChangedClipboardStatus);
}
}
#override
void dispose() {
super.dispose();
widget.clipboardStatus?.removeListener(_onChangedClipboardStatus);
}
#override
Widget build(BuildContext context) {
// If there are no buttons to be shown, don't render anything.
if (widget.handleCut == null &&
widget.handleCopy == null &&
widget.handlePaste == null &&
widget.handleSelectAll == null) {
return const SizedBox.shrink();
}
// If the paste button is desired, don't render anything until the state of
// the clipboard is known, since it's used to determine if paste is shown.
if (widget.handlePaste != null &&
widget.clipboardStatus?.value == ClipboardStatus.unknown) {
return const SizedBox.shrink();
}
// Calculate the positioning of the menu. It is placed above the selection
// if there is enough room, or otherwise below.
final TextSelectionPoint startTextSelectionPoint = widget.endpoints[0];
final TextSelectionPoint endTextSelectionPoint =
widget.endpoints.length > 1 ? widget.endpoints[1] : widget.endpoints[0];
final Offset anchorAbove = Offset(
widget.globalEditableRegion.left + widget.selectionMidpoint.dx,
widget.globalEditableRegion.top +
startTextSelectionPoint.point.dy -
widget.textLineHeight -
_kToolbarContentDistance,
);
final Offset anchorBelow = Offset(
widget.globalEditableRegion.left + widget.selectionMidpoint.dx,
widget.globalEditableRegion.top +
endTextSelectionPoint.point.dy +
_kToolbarContentDistanceBelow,
);
// Determine which buttons will appear so that the order and total number is
// known. A button's position in the menu can slightly affect its
// appearance.
assert(debugCheckHasMaterialLocalizations(context));
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
final List<_TextSelectionToolbarItemData> itemDatas =
<_TextSelectionToolbarItemData>[
if (widget.handleCut != null)
_TextSelectionToolbarItemData(
label: localizations.cutButtonLabel,
onPressed: widget.handleCut!,
),
if (widget.handleCopy != null)
_TextSelectionToolbarItemData(
label: localizations.copyButtonLabel,
onPressed: widget.handleCopy!,
),
if (widget.handlePaste != null &&
widget.clipboardStatus?.value == ClipboardStatus.pasteable)
_TextSelectionToolbarItemData(
label: localizations.pasteButtonLabel,
onPressed: widget.handlePaste!,
),
if (widget.handleSelectAll != null)
_TextSelectionToolbarItemData(
label: localizations.selectAllButtonLabel,
onPressed: widget.handleSelectAll!,
),
];
// If there is no option available, build an empty widget.
if (itemDatas.isEmpty) {
return const SizedBox(width: 0.0, height: 0.0);
}
return TextSelectionToolbar(
anchorAbove: anchorAbove,
anchorBelow: anchorBelow,
children: [
...itemDatas
.asMap()
.entries
.map((MapEntry<int, _TextSelectionToolbarItemData> entry) {
return TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(
entry.key, itemDatas.length),
onPressed: entry.value.onPressed,
child: Text(entry.value.label),
);
}),
],
);
}
}
class _TextSelectionToolbarItemData {
const _TextSelectionToolbarItemData({
required this.label,
required this.onPressed,
});
final String label;
final VoidCallback onPressed;
}
I was able to customize the SelectionToolbar using the toolbarBuilder. You may encounter an error in which case you should use the widget used in the Material widget:
return TextSelectionToolbar(
anchorAbove: anchorAbove,
anchorBelow: anchorBelow,
toolbarBuilder: (context, _) => Material(
child: SizedBox(
width: 230,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Material(child: ListTile())
],
),
),
),
children: const [SizedBox.shrink()],
);
Customizing the TextSelectionToolbar is the best solution I found:
import 'package:flutter/material.dart';
class CostumeSelectionToolbar extends TextSelectionToolbar {
const CostumeSelectionToolbar({
super.key,
required super.anchorAbove,
required super.anchorBelow,
required super.children,
});
static const double _kToolbarScreenPadding = 8.0;
static const double _kToolbarHeight = 275.0;
#override
Widget build(BuildContext context) {
final double paddingAbove =
MediaQuery.of(context).padding.top + _kToolbarScreenPadding;
final double availableHeight = anchorAbove.dy - paddingAbove;
final bool fitsAbove = _kToolbarHeight <= availableHeight;
final Offset localAdjustment = Offset(_kToolbarScreenPadding, paddingAbove);
return Padding(
padding: EdgeInsets.fromLTRB(
_kToolbarScreenPadding,
paddingAbove,
_kToolbarScreenPadding,
_kToolbarScreenPadding,
),
child: Stack(
children: <Widget>[
CustomSingleChildLayout(
delegate: TextSelectionToolbarLayoutDelegate(
anchorAbove: anchorAbove - localAdjustment,
anchorBelow: anchorBelow - localAdjustment,
fitsAbove: fitsAbove,
),
child: SizedBox(
width: 230,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
),
),
],
),
);
}
}
Hi is there a way to somewhat "force" align the PageController to the left? What I want is to have it aligned to the left like the Exlore Categories without actually resizing the card
other options is to make it start in the 2nd card but I don't know how to do this one but I've tried changing initialPage to 1 to no avail
what I tried so far:
adjust the viewportFraction but this resizes the card
- Using Align widget with Alignment set to left -nothing
my code looks like this:
PageController _pageController = PageController(initialPage: 0, viewportFraction: 0.75, );
//few more codes here
//cards
return AnimatedBuilder(
animation: _pageController,
builder: (BuildContext context, Widget widget) {
double value = 1;
if (_pageController.position.haveDimensions) {
value = _pageController.page - index;
value = (1 - (value.abs() * 0.3)).clamp(0.0, 1.0);
}
return Container(
height: 200,
child: Padding(
padding: EdgeInsets.only(left: 3),
child: SizedBox(
height: 200,
width: double.infinity,
child: widget,
),
),
);
},
Set your initialPage state in initState
PageController controller;
#override
void initState() {
super.initState();
controller = PageController(initialPage: 1, viewportFraction: 0.75);
}
I'm new to Dart/Flutter framework and I'm still exploring their possibilities.
I know in Android it's possible to take a picture and extract the main color value from it programmatically. (Android example)
I wonder, how would this be achieved in pure Dart? I would like it to be compatible with both iOS and Android operating system.
Here's a simple function which returns the dominant color given an ImageProvider. This shows the basic usage of Palette Generator without all the boilerplate.
import 'package:palette_generator/palette_generator.dart';
// Calculate dominant color from ImageProvider
Future<Color> getImagePalette (ImageProvider imageProvider) async {
final PaletteGenerator paletteGenerator = await PaletteGenerator
.fromImageProvider(imageProvider);
return paletteGenerator.dominantColor.color;
}
Then use FutureBuilder on the output to build a Widget.
I probably think you got a fix but for future searches to this question, I suggest you check Pallete Generator by the flutter team.
I will try and give a simple explanation of how the code works but for a detailed example head over to the plugin's GitHub repo.
The example below is going to take an image then select the dominant colors from it and then display the colors
First, we add the required imports
import 'package:palette_generator/palette_generator.dart';
After that let's create the main application class.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
...
home: const HomePage(
title: 'Colors from image',
image: AssetImage('assets/images/artwork_default.png',),
imageSize: Size(256.0, 170.0),
...
),
);
}
}
In the image field above, place the image that you want to extract the dominant colors from, i used the image shown here.
Next, we create the HomePage class
#immutable
class HomePage extends StatefulWidget {
/// Creates the home page.
const HomePage({
Key key,
this.title,
this.image,
this.imageSize,
}) : super(key: key);
final String title; //App title
final ImageProvider image; //Image provider to load the colors from
final Size imageSize; //Image dimensions
#override
_HomePageState createState() {
return _HomePageState();
}
}
Lets create the _HomePageState too
class _HomePageState extends State<HomePage> {
Rect region;
PaletteGenerator paletteGenerator;
final GlobalKey imageKey = GlobalKey();
#override
void initState() {
super.initState();
region = Offset.zero & widget.imageSize;
_updatePaletteGenerator(region);
}
Future<void> _updatePaletteGenerator(Rect newRegion) async {
paletteGenerator = await PaletteGenerator.fromImageProvider(
widget.image,
size: widget.imageSize,
region: newRegion,
maximumColorCount: 20,
);
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _kBackgroundColor,
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new AspectRatio(
aspectRatio: 15 / 15,
child: Image(
key: imageKey,
image: widget.image,
),
),
Expanded(child: Swatches(generator: paletteGenerator)),
],
),
);
}
}
The code above just lays out the image and the Swatches which is a class defined below. In initState, we first select a region which the colors will be derived from which in our case is the whole image.
After that we create a class Swatches which receives a PalleteGenerator and draws the swatches for it.
class Swatches extends StatelessWidget {
const Swatches({Key key, this.generator}) : super(key: key);
// The PaletteGenerator that contains all of the swatches that we're going
// to display.
final PaletteGenerator generator;
#override
Widget build(BuildContext context) {
final List<Widget> swatches = <Widget>[];
//The generator field can be null, if so, we return an empty container
if (generator == null || generator.colors.isEmpty) {
return Container();
}
//Loop through the colors in the PaletteGenerator and add them to the list of swatches above
for (Color color in generator.colors) {
swatches.add(PaletteSwatch(color: color));
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
//All the colors,
Wrap(
children: swatches,
),
//The colors with ranking
Container(height: 30.0),
PaletteSwatch(label: 'Dominant', color: generator.dominantColor?.color),
PaletteSwatch(
label: 'Light Vibrant', color: generator.lightVibrantColor?.color),
PaletteSwatch(label: 'Vibrant', color: generator.vibrantColor?.color),
PaletteSwatch(
label: 'Dark Vibrant', color: generator.darkVibrantColor?.color),
PaletteSwatch(
label: 'Light Muted', color: generator.lightMutedColor?.color),
PaletteSwatch(label: 'Muted', color: generator.mutedColor?.color),
PaletteSwatch(
label: 'Dark Muted', color: generator.darkMutedColor?.color),
],
);
}
}
After that lets create a PaletteSwatch class. A palette swatch is just a square of color with an optional label
#immutable
class PaletteSwatch extends StatelessWidget {
// Creates a PaletteSwatch.
//
// If the [color] argument is omitted, then the swatch will show a
// placeholder instead, to indicate that there is no color.
const PaletteSwatch({
Key key,
this.color,
this.label,
}) : super(key: key);
// The color of the swatch. May be null.
final Color color;
// The optional label to display next to the swatch.
final String label;
#override
Widget build(BuildContext context) {
// Compute the "distance" of the color swatch and the background color
// so that we can put a border around those color swatches that are too
// close to the background's saturation and lightness. We ignore hue for
// the comparison.
final HSLColor hslColor = HSLColor.fromColor(color ?? Colors.transparent);
final HSLColor backgroundAsHsl = HSLColor.fromColor(_kBackgroundColor);
final double colorDistance = math.sqrt(
math.pow(hslColor.saturation - backgroundAsHsl.saturation, 2.0) +
math.pow(hslColor.lightness - backgroundAsHsl.lightness, 2.0));
Widget swatch = Padding(
padding: const EdgeInsets.all(2.0),
child: color == null
? const Placeholder(
fallbackWidth: 34.0,
fallbackHeight: 20.0,
color: Color(0xff404040),
strokeWidth: 2.0,
)
: Container(
decoration: BoxDecoration(
color: color,
border: Border.all(
width: 1.0,
color: _kPlaceholderColor,
style: colorDistance < 0.2
? BorderStyle.solid
: BorderStyle.none,
)),
width: 34.0,
height: 20.0,
),
);
if (label != null) {
swatch = ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 130.0, minWidth: 130.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
swatch,
Container(width: 5.0),
Text(label),
],
),
);
}
return swatch;
}
}
Hope this helps, thank you.
//////////////////////////////
//
// 2019, roipeker.com
// screencast - demo simple image:
// https://youtu.be/EJyRH4_pY8I
//
// screencast - demo snapshot:
// https://youtu.be/-LxPcL7T61E
//
//////////////////////////////
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:image/image.dart' as img;
import 'package:flutter/services.dart' show rootBundle;
void main() => runApp(const MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String imagePath = 'assets/5.jpg';
GlobalKey imageKey = GlobalKey();
GlobalKey paintKey = GlobalKey();
// CHANGE THIS FLAG TO TEST BASIC IMAGE, AND SNAPSHOT.
bool useSnapshot = true;
// based on useSnapshot=true ? paintKey : imageKey ;
// this key is used in this example to keep the code shorter.
late GlobalKey currentKey;
final StreamController<Color> _stateController = StreamController<Color>();
//late img.Image photo ;
img.Image? photo;
#override
void initState() {
currentKey = useSnapshot ? paintKey : imageKey;
super.initState();
}
#override
Widget build(BuildContext context) {
final String title = useSnapshot ? "snapshot" : "basic";
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text("Color picker $title")),
body: StreamBuilder(
initialData: Colors.green[500],
stream: _stateController.stream,
builder: (buildContext, snapshot) {
Color selectedColor = snapshot.data as Color ?? Colors.green;
return Stack(
children: <Widget>[
RepaintBoundary(
key: paintKey,
child: GestureDetector(
onPanDown: (details) {
searchPixel(details.globalPosition);
},
onPanUpdate: (details) {
searchPixel(details.globalPosition);
},
child: Center(
child: Image.asset(
imagePath,
key: imageKey,
//color: Colors.red,
//colorBlendMode: BlendMode.hue,
//alignment: Alignment.bottomRight,
fit: BoxFit.contain,
//scale: .8,
),
),
),
),
Container(
margin: const EdgeInsets.all(70),
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor!,
border: Border.all(width: 2.0, color: Colors.white),
boxShadow: [
const BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2))
]),
),
Positioned(
child: Text('${selectedColor}',
style: const TextStyle(
color: Colors.white,
backgroundColor: Colors.black54)),
left: 114,
top: 95,
),
],
);
}),
),
);
}
void searchPixel(Offset globalPosition) async {
if (photo == null) {
await (useSnapshot ? loadSnapshotBytes() : loadImageBundleBytes());
}
_calculatePixel(globalPosition);
}
void _calculatePixel(Offset globalPosition) {
RenderBox box = currentKey.currentContext!.findRenderObject() as RenderBox;
Offset localPosition = box.globalToLocal(globalPosition);
double px = localPosition.dx;
double py = localPosition.dy;
if (!useSnapshot) {
double widgetScale = box.size.width / photo!.width;
print(py);
px = (px / widgetScale);
py = (py / widgetScale);
}
int pixel32 = photo!.getPixelSafe(px.toInt(), py.toInt());
int hex = abgrToArgb(pixel32);
_stateController.add(Color(hex));
}
Future<void> loadImageBundleBytes() async {
ByteData imageBytes = await rootBundle.load(imagePath);
setImageBytes(imageBytes);
}
Future<void> loadSnapshotBytes() async {
RenderRepaintBoundary boxPaint =
paintKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
//RenderObject? boxPaint = paintKey.currentContext.findRenderObject();
ui.Image capture = await boxPaint.toImage();
ByteData? imageBytes =
await capture.toByteData(format: ui.ImageByteFormat.png);
setImageBytes(imageBytes!);
capture.dispose();
}
void setImageBytes(ByteData imageBytes) {
List<int> values = imageBytes.buffer.asUint8List();
photo;
photo = img.decodeImage(values)!;
}
}
// image lib uses uses KML color format, convert #AABBGGRR to regular #AARRGGBB
int abgrToArgb(int argbColor) {
int r = (argbColor >> 16) & 0xFF;
int b = argbColor & 0xFF;
return (argbColor & 0xFF00FF00) | (b << 16) | r;
}