Android RecyclerView using VideoView in ViewHolder - android-recyclerview

I want to play video using RecyclerView and using recyclerView.smoothScrollToPosition() scroll page.
When RecyclerView using VideoView in ViewHolder, onBindViewHolder(ViewHolder, position) position can't match RecyclerView current position, so I can't control VideoView.
Adapter:
interface Control {
int getCurrentPosition() ;
}
Control control;
public RecyclerView.ViewHolder onCreateViewHolder() {
Log.i(TAG, "onCreateViewHolder()...page: " + control.getCurrentPosition() );
}
public void onBindViewHolder(ViewHolder, position){
Log.i(TAG, "onBindViewHolder()...position: " + position + ", page: " + control.getCurrentPosition() );
int page = control.getCurrentPosition();
// position maybe difference page value
}
Activity:
int page;
void onCreate() {
adapter = new Adapter(new Adapter.Control() {
#Override
public int getCurrentPosition() {
return page;
}
});
}
void scroll() {
Log.i(TAG, "page: " + page );
recyclerView.smoothScrollToPosition(page);
}
Log:
page: 0
onCreateViewHolder()...page: 0
onBindViewHolder()...position: 0, page: 0
page: 1
onCreateViewHolder()...page: 1
onBindViewHolder()...position: 1, page: 1
onCreateViewHolder()...page: 1
onBindViewHolder()...position: 2, page: 1
page: 2
onCreateViewHolder()...page: 2
onBindViewHolder()...position: 3, page: 2
page: 3
page: 4
onBindViewHolder()...position: 4, page: 4
onBindViewHolder()...position: 5, page: 4
page: 5
page: 6
onBindViewHolder()...position: 6, page: 6
onBindViewHolder()...position: 7, page: 6
page: 7
page: 8
onBindViewHolder()...position: 8, page: 8
onBindViewHolder()...position: 9, page: 8
page: 9

Related

Flutter fileimage not appearing in page flip animation widget

I want to create a book app like google's play book application
my books are in pdf, Im getting them from Firebase storage, then im converting that pdf to image, page by page, its working fine
but when I try to put the file inside the PageFlip widget, it doesn't work as intended... it starts converting pdf to images, all good, then shows blank screen, when I press ctrl+s to refresh, then it does the conversion again, then it works
when I restart the app, again first time shows blank screen, after refresh it works fine
I can swipe through the images and animation works fine on them, but some pages are still blank, they're not rendered as image
for example, im on page 1 I swipe, page 2 (shows fine), again, page 3 (shows fine).... page 22(is blank/not rendered), page 23(shows fine)
the animation is a bit laggy too, not as smooth as playbooks application,
can someone help please?
My code
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:internet_file/internet_file.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdfx/pdfx.dart';
import 'package:show_up_animation/show_up_animation.dart';
import '../helpers/ui_control.dart';
import '../models/book.dart';
import '../providers/book.dart';
import '../providers/loading.dart';
import '../providers/user.dart';
import '../widgets/featured_book.dart';
import '../widgets/page_flip.dart';
import '../widgets/slide_route.dart';
class BookView extends StatefulWidget {
const BookView({Key? key, required this.book, required this.currentPage})
: super(key: key);
final BookModel book;
final int currentPage;
#override
_BookViewState createState() => _BookViewState();
}
class _BookViewState extends State<BookView> {
late int page;
bool show = true;
Future<List<File>> pdfToImages(Future<PdfDocument> pdf) async {
final pdfDoc = await pdf;
List<File> images = [];
print('converting pdf to image');
for (int i = 1; i <= pdfDoc.pagesCount; i++) {
final tempDir = await getTemporaryDirectory();
File file =
File('${tempDir.path}/${widget.book.id}-${widget.book.title}-$i.png');
if (!file.existsSync()) {
final page = await pdfDoc.getPage(i);
final image = await page.render(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
backgroundColor: "#EFEADD",
);
Uint8List imageInUnit8List = image!.bytes;
file.writeAsBytesSync(imageInUnit8List);
page.close();
}
images.add(file);
}
return images;
}
#override
void initState() {
super.initState();
page = widget.currentPage;
}
void _onTap() {
setState(() {
show = !show;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: FutureBuilder<List<File>>(
future: pdfToImages(
PdfDocument.openData(InternetFile.get(widget.book.path))),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _onTap,
child: InteractiveViewer(
child: PageFlipWidget(
backgroundColor: const Color(0xFFEFEADD),
lastPage: Image.file(
snapshot.data!.elementAt(widget.book.pageCount - 1)),
onMiddleTap: _onTap,
children: List.generate(widget.book.pageCount, (index) {
return Image.file(
snapshot.data!.elementAt(index),
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
);
}),
),
),
);
}),
);
}
}
Page flip animation widget
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
class PageFlipWidget extends StatefulWidget {
const PageFlipWidget({
Key? key,
this.duration = const Duration(milliseconds: 450),
this.cutoff = 0.6,
this.backgroundColor = const Color(0xFFFFFFFF),
required this.children,
this.initialIndex = 0,
this.lastPage,
this.showDragCutoff = false,
required this.onMiddleTap,
}) : super(key: key);
final Color backgroundColor;
final List\<Widget\> children;
final Duration duration;
final int initialIndex;
final Widget? lastPage;
final bool showDragCutoff;
final double cutoff;
final VoidCallback onMiddleTap;
#override
PageFlipWidgetState createState() =\> PageFlipWidgetState();
}
class PageFlipWidgetState extends State\<PageFlipWidget\>
with TickerProviderStateMixin {
int pageNumber = 0;
List\<Widget\>? pages = \[\];
final List\<AnimationController\> \_controllers = \[\];
bool? \_isForward;
#override
void didUpdateWidget(PageFlipWidget oldWidget) {
if (oldWidget.children != widget.children) {
\_setUp();
}
if (oldWidget.duration != widget.duration) {
\_setUp();
}
if (oldWidget.backgroundColor != widget.backgroundColor) {
\_setUp();
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
for (var c in \_controllers) {
c.dispose();
}
super.dispose();
}
#override
void initState() {
super.initState();
\_setUp();
}
void \_setUp() {
\_controllers.clear();
pages?.clear();
for (var i = 0; i < widget.children.length; i++) {
final controller = AnimationController(
value: 1,
duration: widget.duration,
vsync: this,
);
_controllers.add(controller);
final child = PageFlipBuilder(
backgroundColor: widget.backgroundColor,
amount: controller,
child: widget.children[i],
);
pages?.add(child);
}
pages = pages?.reversed.toList();
pageNumber = widget.initialIndex;
}
bool get \_isLastPage =\>
pages != null && (pages?.length ?? 0 - 1) == pageNumber;
bool get \_isFirstPage =\> pageNumber == 0;
void \_flipPage(DragUpdateDetails details, BoxConstraints dimens) {
final ratio = details.delta.dx / dimens.maxWidth;
if (\_isForward == null) {
if (details.delta.dx \> 0) {
\_isForward = false;
} else {
\_isForward = true;
}
}
if (\_isForward! || pageNumber == 0) {
\_isLastPage ? null : \_controllers\[pageNumber\].value += ratio;
} else {
\_controllers\[pageNumber - 1\].value += ratio;
}
}
Future \_onDragFinish() async {
if (\_isForward != null) {
if (\_isForward!) {
if (!\_isLastPage &&
\_controllers\[pageNumber\].value \<= (widget.cutoff + 0.15)) {
await nextPage();
} else {
\_isLastPage ? null : await \_controllers\[pageNumber\].forward();
}
} else {
if (!\_isFirstPage &&
\_controllers\[pageNumber - 1\].value \>= widget.cutoff) {
await previousPage();
} else {
if (\_isFirstPage) {
await \_controllers\[pageNumber\].forward();
} else {
\_isFirstPage ? null : await \_controllers\[pageNumber - 1\].reverse();
}
}
}
}
\_isForward = null;
}
Future nextPage() async {
await \_controllers\[pageNumber\].reverse();
if (mounted) {
setState(() {
pageNumber++;
});
}
}
Future previousPage() async {
await \_controllers\[pageNumber - 1\].forward();
if (mounted) {
setState(() {
pageNumber--;
});
}
}
Future goToPage(int index) async {
if (mounted) {
setState(() {
pageNumber = index;
});
}
for (var i = 0; i \< \_controllers.length; i++) {
if (i == index) {
\_controllers\[i\].forward();
} else if (i \< index) {
// \_controllers\[i\].value = 0;
\_controllers\[i\].reverse();
} else {
if (\_controllers\[i\].status == AnimationStatus.reverse) {
\_controllers\[i\].value = 1;
}
}
}
}
#override
Widget build(BuildContext context) {
return Material(
child: LayoutBuilder(
builder: (context, dimens) =\> GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragCancel: () =\> \_isForward = null,
onHorizontalDragUpdate: (details) =\> \_flipPage(details, dimens),
onHorizontalDragEnd: (details) =\> \_onDragFinish(),
child: Stack(
fit: StackFit.expand,
children: \<Widget\>\[
if (widget.lastPage != null) ...\[
widget.lastPage!,
\],
...pages!,
Positioned.fill(
child: Flex(
direction: Axis.horizontal,
children: \<Widget\>\[
Flexible(
flex: (widget.cutoff \* 0.75 \* 10).round(),
child: Container(
color: widget.showDragCutoff
? Colors.blue.withAlpha(100)
: null,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: \_isFirstPage ? null : previousPage,
),
),
),
Flexible(
flex: 10,
child: Container(
color: widget.showDragCutoff
? Colors.orange.withAlpha(100)
: null,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: widget.onMiddleTap,
),
),
),
Flexible(
flex: (widget.cutoff \* 0.75 \* 10).round(),
child: Container(
color: widget.showDragCutoff
? Colors.red.withAlpha(100)
: null,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: \_isLastPage ? null : nextPage,
),
),
),
\],
),
),
\],
),
),
),
);
}
}
class PageFlipEffect extends CustomPainter {
PageFlipEffect({
required this.amount,
required this.image,
this.backgroundColor,
this.radius = 0.18,
}) : super(repaint: amount);
final Animation\<double\> amount;
final ui.Image image;
final Color? backgroundColor;
final double radius;
#override
void paint(ui.Canvas canvas, ui.Size size) {
final pos = amount.value;
final movX = (1.0 - pos) \* 0.85;
final calcR = (movX \< 0.20) ? radius \* movX \* 5 : radius;
final wHRatio = 1 - calcR;
final hWRatio = image.height / image.width;
final hWCorrection = (hWRatio - 1.0) / 2.0;
final w = size.width.toDouble();
final h = size.height.toDouble();
final c = canvas;
final shadowXf = (wHRatio - movX);
final shadowSigma =
Shadow.convertRadiusToSigma(8.0 + (32.0 * (1.0 - shadowXf)));
final pageRect = Rect.fromLTRB(0.0, 0.0, w * shadowXf, h);
if (backgroundColor != null) {
c.drawRect(pageRect, Paint()..color = backgroundColor!);
}
if (pos != 0) {
c.drawRect(
pageRect,
Paint()
..color = Colors.black54
..maskFilter = MaskFilter.blur(BlurStyle.outer, shadowSigma),
);
}
final ip = Paint();
for (double x = 0; x < size.width; x++) {
final xf = (x / w);
final v = (calcR * (math.sin(math.pi / 0.5 * (xf - (1.0 - pos)))) +
(calcR * 1.1));
final xv = (xf * wHRatio) - movX;
final sx = (xf * image.width);
final sr = Rect.fromLTRB(sx, 0.0, sx + 1.0, image.height.toDouble());
final yv = ((h * calcR * movX) * hWRatio) - hWCorrection;
final ds = (yv * v);
final dr = Rect.fromLTRB(xv * w, 0.0 - ds, xv * w + 1.0, h + ds);
c.drawImageRect(image, sr, dr, ip);
}
}
#override
bool shouldRepaint(PageFlipEffect oldDelegate) {
return oldDelegate.image != image ||
oldDelegate.amount.value != amount.value;
}
}
class PageFlipBuilder extends StatefulWidget {
const PageFlipBuilder({
Key? key,
required this.amount,
this.backgroundColor = const Color(0xFFFFFFCC),
this.child,
}) : super(key: key);
final Animation\<double\> amount;
final Color backgroundColor;
final Widget? child;
#override
State\<PageFlipBuilder\> createState() =\> \_PageFlipBuilderState();
}
class \_PageFlipBuilderState extends State\<PageFlipBuilder\> {
final \_boundaryKey = GlobalKey();
ui.Image? \_image;
#override
void didUpdateWidget(PageFlipBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.child != widget.child) {
\_image = null;
}
}
void \_captureImage(Duration timeStamp) async {
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
final boundary = \_boundaryKey.currentContext?.findRenderObject()
as RenderRepaintBoundary;
if (boundary.debugNeedsPaint) {
await Future.delayed(const Duration(milliseconds: 20));
return \_captureImage(timeStamp);
}
final image = await boundary.toImage(pixelRatio: pixelRatio);
setState(() =\> \_image = image);
}
#override
Widget build(BuildContext context) {
if (\_image != null) {
return CustomPaint(
painter: PageFlipEffect(
amount: widget.amount,
image: \_image!,
backgroundColor: widget.backgroundColor,
),
size: Size.infinite,
);
} else {
WidgetsBinding.instance.addPostFrameCallback(\_captureImage);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final size = constraints.biggest;
return Stack(
clipBehavior: Clip.hardEdge,
children: \<Widget\>\[
Positioned(
left: 1 + size.width,
top: 1 + size.height,
width: size.width,
height: size.height,
child: RepaintBoundary(
key: \_boundaryKey,
child: widget.child,
),
),
\],
);
},
);
}
}
}
class PageFlipImage extends StatefulWidget {
const PageFlipImage({
Key? key,
required this.amount,
this.image,
this.backgroundColor = const Color(0xFFFFFFCC),
}) : super(key: key);
final Animation\<double\> amount;
final ImageProvider? image;
final Color? backgroundColor;
#override
State\<PageFlipImage\> createState() =\> \_PageFlipImageState();
}
class \_PageFlipImageState extends State\<PageFlipImage\> {
ImageStream? \_imageStream;
ImageInfo? \_imageInfo;
bool \_isListeningToStream = false;
late ImageStreamListener \_imageListener;
#override
void initState() {
super.initState();
\_imageListener = ImageStreamListener(\_handleImageFrame);
}
#override
void dispose() {
\_stopListeningToStream();
super.dispose();
}
#override
void didChangeDependencies() {
\_resolveImage();
if (TickerMode.of(context)) {
\_listenToStream();
} else {
\_stopListeningToStream();
}
super.didChangeDependencies();
}
#override
void didUpdateWidget(PageFlipImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.image != oldWidget.image) {
\_resolveImage();
}
}
#override
void reassemble() {
\_resolveImage(); // in case the image cache was flushed
super.reassemble();
}
void \_resolveImage() {
final ImageStream newStream =
widget.image!.resolve(createLocalImageConfiguration(context));
\_updateSourceStream(newStream);
}
void \_handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
setState(() =\> \_imageInfo = imageInfo);
}
// Updates \_imageStream to newStream, and moves the stream listener
// registration from the old stream to the new stream (if a listener was
// registered).
void \_updateSourceStream(ImageStream newStream) {
if (\_imageStream?.key == newStream.key) return;
if (_isListeningToStream) _imageStream?.removeListener(_imageListener);
_imageStream = newStream;
if (_isListeningToStream) _imageStream?.addListener(_imageListener);
}
void \_listenToStream() {
if (\_isListeningToStream) return;
\_imageStream?.addListener(\_imageListener);
\_isListeningToStream = true;
}
void \_stopListeningToStream() {
if (!\_isListeningToStream) return;
\_imageStream?.removeListener(\_imageListener);
\_isListeningToStream = false;
}
#override
Widget build(BuildContext context) {
if (\_imageInfo != null) {
return CustomPaint(
painter: PageFlipEffect(
amount: widget.amount,
image: \_imageInfo!.image,
backgroundColor: widget.backgroundColor,
),
size: Size.infinite,
);
} else {
return const SizedBox();
}
}
}
in the beginning I was converting the pdf page to uint8list and using the image.memory widget, and it was not working swell, so I converted the uint8list bytes into file and then using image.file, it started getting this issue where it first it blank then after ctrl+s it starts rendering but some pages still not rendered
I think image.asset works well, but idk if that's helpful, I need my files on firebase storage.

Get CGRect of View

I'm using a "RectGetter" to get the CGRect of a View.
Like this:
Text("Hello")
.background(RectGetter(rect: self.$rect))
struct RectGetter: View {
#Binding var rect: CGRect
var body: some View {
GeometryReader { proxy in
self.createView(proxy: proxy)
}
}
func createView(proxy: GeometryProxy) -> some View {
DispatchQueue.main.async {
self.rect = proxy.frame(in: .global) // crash here
}
return Rectangle().fill(Color.clear)
}
}
But sometimes I get a crash when setting the rect.
Thread 0 name:
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x00000001a1ec5ec4 __pthread_kill + 8
1 libsystem_pthread.dylib 0x00000001a1de5724 pthread_kill$VARIANT$armv81 + 216 (pthread.c:1458)
2 libsystem_c.dylib 0x00000001a1d358c0 __abort + 112 (abort.c:147)
3 libsystem_c.dylib 0x00000001a1d35850 abort + 112 (abort.c:118)
4 AttributeGraph 0x00000001cce56548 0x1cce25000 + 202056
5 AttributeGraph 0x00000001cce2b980 0x1cce25000 + 27008
6 SwiftUI 0x00000001d89ce9b4 $s7SwiftUI27_PositionAwareLayoutContextV10dimensionsSo6CGSizeVvg + 56 (<compiler-generated>:0)
7 SwiftUI 0x00000001d8a43584 $sSo6CGSizeVIgd_ABIegr_TRTA + 24 (<compiler-generated>:0)
8 SwiftUI 0x00000001d8a43a54 $s7SwiftUI13GeometryProxyV4sync33_4C4BAC551E328ACCA9CD3748EDC0CC3ALLyxSgxyXElFxAA9ViewGraphCXEfU_... + 92 (GeometryReader.swift:126)
9 SwiftUI 0x00000001d8a43f20 $s7SwiftUI13GeometryProxyV4sync33_4C4BAC551E328ACCA9CD3748EDC0CC3ALLyxSgxyXElFxAA9ViewGraphCXEfU_... + 20 (<compiler-generated>:0)
10 SwiftUI 0x00000001d8c4842c $s7SwiftUI16ViewRendererHostPAAE06updateC5Graph4bodyqd__qd__AA0cG0CXE_tlF + 80 (ViewRendererHost.swift:64)
11 SwiftUI 0x00000001d8a438d4 $s7SwiftUI13GeometryProxyV5frame2inSo6CGRectVAA15CoordinateSpaceO_tF + 196 (GeometryReader.swift:125)
12 MyApp 0x0000000102cf5c54 closure #1 in RectGetter.createView(proxy:) + 128 (RectGetter.swift:22)
Is there some more reliable way (not crashing) to get the CGRect of a View?
Update: improved and simplified a possible solution to read rect of any view in any coordinate space via helper extension. Works since Xcode 11.1, retested with Xcode 13.3.
Main part:
func rectReader(_ binding: Binding<CGRect>, _ space: CoordinateSpace = .global) -> some View {
GeometryReader { (geometry) -> Color in
let rect = geometry.frame(in: space)
DispatchQueue.main.async {
binding.wrappedValue = rect
}
return .clear
}
}
Usage the same
Text("Test").background(rectReader($rect))
or with new extension
Text("Test").reading(rect: $rect)
Complete findings and code is here
Another way is with Preference Keys, as outlined here.
In summary, you can set up a modifier:
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
struct SizeModifier: ViewModifier {
private var sizeView: some View {
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self, value: geometry.frame(in: .global))
}
}
func body(content: Content) -> some View {
content.background(sizeView)
}
}
Then use that one:
SomeView()
.modifier(SizeModifier())
.onPreferenceChange(SizePreferenceKey.self) { print("My rect is: \($0)") }

How to show a badges count of ToolBarItem Icon in Xamarin Forms

It is not about how to show notification badges nor it's about to show toolbar item icon. It is clear question that how to show a badges count on a toolbar item icon. ?
I am sharing code to create ToolbarItem with icon in XF content page:
In cs File:
ToolbarItem cartItem = new ToolbarItem();
scanItem.Text = "My Cart";
scanItem.Order = ToolbarItemOrder.Primary;
scanItem.Icon = "carticon.png";
ToolbarItems.Add(cartItem );
In Xaml File:
<ContentPage.ToolbarItems>
<ToolbarItem Text="Cart" Priority="0" x:Name="menu1">
</ToolbarItem>
</ContentPage.ToolbarItems>
Now I want to Place a badge count on the above added tool bar item icon. How it can be achieved ?
Placing badge icon's in the native toolbars is actually more effort than its worth. If I need a badge icon, I remove the navigation page.
NavigationPage.SetHasNavigationBar(myPageInstance, false);
Then I create my own toolbar from scratch. In this toolbar, I can overlay an image in there, you can also place a number in it as needed. For example.
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding IconCommand}" />
</Grid.GestureRecognizers>
<iconize:IconImage
Icon="fa-drawer"
IconColor="white"
IconSize="20" />
<Grid Margin="15,-15,0,0">
<iconize:IconImage Grid.Row="0"
HeightRequest="40"
WidthRequest="40"
Icon="fa-circle"
IconColor="red"
IsVisible="{Binding IsCircleVisible}"
IconSize="10" />
</Grid>
</Grid>
I use Iconize wtih FontAwesome for the icons
With the help of Xamarin Forum Discussion, I have achieved it. Read ad understand the complete discussion before implement it. Thank you "Slava Chernikoff", "Emanuele Sabetta", "Mirza Sikander", "Satish" to discuss and yours share code.
Setp 1: Create a Helper Class in PCL and install NGraphics package from nugget.
public class CartIconHelper
{
private static Graphic _svgGraphic = null;
public const string ResourcePath = "ToolBarAndroidBadge.Resources.cartIcon.svg";
private static PathOp[] RoundRect(NGraphics.Rect rect, double radius)
{
return new PathOp[]
{
new NGraphics.MoveTo(rect.X + radius, rect.Y),
new NGraphics.LineTo(rect.X + rect.Width - radius, rect.Y),
new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + rect.Width, rect.Y + radius)),
new NGraphics.LineTo(rect.X + rect.Width, rect.Y + rect.Height - radius),
new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + rect.Width - radius, rect.Y + rect.Height)),
new NGraphics.LineTo(rect.X + radius, rect.Y + rect.Height),
new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X, rect.Y + rect.Height - radius)),
new NGraphics.LineTo(rect.X, rect.Y + radius), new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + radius, rect.Y)),
new NGraphics.ClosePath()
};
}
public static string DrawCartIcon(int count, string path, double iconSize = 30, double scale = 2, string fontName = "Arial", double fontSize = 12, double textSpacing = 4)
{
var service = DependencyService.Get<IService>();
var canvas = service.GetCanvas();
if (_svgGraphic == null) using (var stream = typeof(CartIconHelper).GetTypeInfo().Assembly.GetManifestResourceStream(path))
_svgGraphic = new SvgReader(new StreamReader(stream)).Graphic;
//st = ReadFully(stream);
var minSvgScale = Math.Min(canvas.Size.Width / _svgGraphic.Size.Width, canvas.Size.Height / _svgGraphic.Size.Height) / 1.15;
var w = _svgGraphic.Size.Width / minSvgScale;
var h = _svgGraphic.Size.Height / minSvgScale;
_svgGraphic.ViewBox = new NGraphics.Rect(0, -14, w, h);
_svgGraphic.Draw(canvas);
if (count > 0)
{
var text = count > 99 ? "99+" : count.ToString();
var font = new NGraphics.Font(fontName, fontSize);
var textSize = canvas.MeasureText(text, font);
var textRect = new NGraphics.Rect(canvas.Size.Width - textSize.Width - textSpacing, textSpacing, textSize.Width, textSize.Height);
if (count < 10)
{
var side = Math.Max(textSize.Width, textSize.Height);
var elipseRect = new NGraphics.Rect(canvas.Size.Width - side - 2 * textSpacing, 0, side + 2 * textSpacing, side + 2 * textSpacing);
canvas.FillEllipse(elipseRect, NGraphics.Colors.Red);
textRect -= new NGraphics.Point(side - textSize.Width, side - textSize.Height) / 2.0;
}
else
{
var elipseRect = new NGraphics.Rect(textRect.Left - textSpacing, textRect.Top - textSpacing, textRect.Width + 2 * textSpacing, textSize.Height + 2 * textSpacing);
canvas.FillPath(RoundRect(elipseRect, 6), NGraphics.Colors.Red);
}
var testReact1= new NGraphics.Rect(20,12,0,0);
// canvas.DrawText(text, textRect + new NGraphics.Point(0, textSize.Height), font, NGraphics.TextAlignment.Center, NGraphics.Colors.Black);
canvas.DrawText("5", testReact1, font, NGraphics.TextAlignment.Left, NGraphics.Colors.White);
}
service.SaveImage(canvas.GetImage());
string imagePath = service.GetImage();
return imagePath;
// return st;
}
}
Step 2: Create a interface to IService in PCL
public interface IService
{
IImageCanvas GetCanvas();
void SaveImage(NGraphics.IImage image);
string GetImage();
}
Step 3 : Implement this interface in your Android project
class CanvasServices:IService
{
private readonly AndroidPlatform _platform;
public CanvasServices()
{
_platform = new AndroidPlatform();
}
public void SaveImage(IImage image)
{
var dir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var filePath = System.IO.Path.Combine(dir, "cart.png");
var stream = new FileStream(filePath, FileMode.Create);
image.SaveAsPng(stream);
//bitmap.Compress(image., 100, stream);
stream.Close();
}
public string GetImage()
{
var dir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var filePath = System.IO.Path.Combine(dir, "cart.png");
using (var streamReader = new StreamReader(filePath))
{
string content = streamReader.ReadToEnd();
System.Diagnostics.Debug.WriteLine(content);
}
return filePath;
}
public IImageCanvas GetCanvas()
{
NGraphics.Size size = new NGraphics.Size(30);
return _platform.CreateImageCanvas(size);
}
public NGraphics.AndroidPlatform GetPlatform()
{
return _platform;
}
}
Setp 4: Now, use CartIcon Helper in your PCL project to show badges in TabBarItem.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
var imagePath = CartIconHelper.DrawCartIcon(2, "ToolBarAndroidBadge.Resources.cartIcon.svg");
string deviceSepecificFolderPath = Device.OnPlatform(null, imagePath, null);
object convertedObject = new FileImageSourceConverter().ConvertFromInvariantString(deviceSepecificFolderPath);
FileImageSource fileImageSource = (FileImageSource)convertedObject;
ToolbarItem cartItem = new ToolbarItem();
cartItem.Text = "My Cart";
cartItem.Order = ToolbarItemOrder.Primary;
cartItem.Icon = fileImageSource;
ToolbarItems.Add(cartItem);
}
}
For any one who wants to add badge on toolbar item using custom ui try,
Instead of using default toolbar item, you can hide the default navigation bar by NavigationPage.SetHasNavigationBar(this, false);
in the constructor.
Then prepare the custom navigation bar with toolbar item with badge as mentioned in above answers.
If you are using master detail page, hiding default navigation bar will hide hamburger icon, so need to slide from left to see sliding menu. Alternate method would be place a button with hamburger icon in custom navigation bar, on button click use messaging center to present the sliding menu.
Example: On page in which hamburger button is clicked
private void Button_Clicked(object sender, System.EventArgs e)
{
MessagingCenter.Send(this, "presnt");
}
On MasterDetail page
MessagingCenter.Subscribe<YourPage>(this, "presnt", (sender) =>
{
IsPresented = true;
});
Before making IsPresented=true, check for sliding menu is not all-ready presented.
Check https://github.com/LeslieCorrea/Xamarin-Forms-Shopping-Cart for badge on toolbar item.
Implement below code to draw a ground circle with text over toolbar icon
BarButtonItemExtensions.cs
using CoreAnimation;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using UIKit;
namespace TeamCollaXform.Views.Services
{
public static class BarButtonItemExtensions
{
enum AssociationPolicy
{
ASSIGN = 0,
RETAIN_NONATOMIC = 1,
COPY_NONATOMIC = 3,
RETAIN = 01401,
COPY = 01403,
}
static NSString BadgeKey = new NSString(#"BadgeKey");
[DllImport(Constants.ObjectiveCLibrary)]
static extern void objc_setAssociatedObject(IntPtr obj, IntPtr key, IntPtr value, AssociationPolicy policy);
[DllImport(Constants.ObjectiveCLibrary)]
static extern IntPtr objc_getAssociatedObject(IntPtr obj, IntPtr key);
static CAShapeLayer GetBadgeLayer(UIBarButtonItem barButtonItem)
{
var handle = objc_getAssociatedObject(barButtonItem.Handle, BadgeKey.Handle);
if (handle != IntPtr.Zero)
{
var value = ObjCRuntime.Runtime.GetNSObject(handle);
if (value != null)
return value as CAShapeLayer;
else
return null;
}
return null;
}
static void DrawRoundedRect(CAShapeLayer layer, CGRect rect, float radius, UIColor color, bool filled)
{
layer.FillColor = filled ? color.CGColor : UIColor.White.CGColor;
layer.StrokeColor = color.CGColor;
layer.Path = UIBezierPath.FromRoundedRect(rect, radius).CGPath;
}
public static void AddBadge(this UIBarButtonItem barButtonItem, string text, UIColor backgroundColor, UIColor textColor, bool filled = true, float fontSize = 11.0f)
{
if (string.IsNullOrEmpty(text))
{
return;
}
CGPoint offset = CGPoint.Empty;
if (backgroundColor == null)
backgroundColor = UIColor.Red;
var font = UIFont.SystemFontOfSize(fontSize);
if (UIDevice.CurrentDevice.CheckSystemVersion(9, 0))
{
font = UIFont.MonospacedDigitSystemFontOfSize(fontSize, UIFontWeight.Regular);
}
var view = barButtonItem.ValueForKey(new NSString(#"view")) as UIView;
var bLayer = GetBadgeLayer(barButtonItem);
bLayer?.RemoveFromSuperLayer();
var badgeSize = text.StringSize(font);
var height = badgeSize.Height;
var width = badgeSize.Width + 5; /* padding */
//make sure we have at least a circle
if (width < height)
{
width = height;
}
//x position is offset from right-hand side
var x = view.Frame.Width - width + offset.X;
var badgeFrame = new CGRect(new CGPoint(x: x - 4, y: offset.Y + 5), size: new CGSize(width: width, height: height));
bLayer = new CAShapeLayer();
DrawRoundedRect(bLayer, badgeFrame, 7.0f, backgroundColor, filled);
view.Layer.AddSublayer(bLayer);
// Initialiaze Badge's label
var label = new CATextLayer();
label.String = text;
label.TextAlignmentMode = CATextLayerAlignmentMode.Center;
label.SetFont(CGFont.CreateWithFontName(font.Name));
label.FontSize = font.PointSize;
label.Frame = badgeFrame;
label.ForegroundColor = filled ? textColor.CGColor : UIColor.White.CGColor;
label.BackgroundColor = UIColor.Clear.CGColor;
label.ContentsScale = UIScreen.MainScreen.Scale;
bLayer.AddSublayer(label);
// Save Badge as UIBarButtonItem property
objc_setAssociatedObject(barButtonItem.Handle, BadgeKey.Handle, bLayer.Handle, AssociationPolicy.RETAIN_NONATOMIC);
}
public static void UpdateBadge(this UIBarButtonItem barButtonItem, string text, UIColor backgroundColor, UIColor textColor)
{
var bLayer = GetBadgeLayer(barButtonItem);
if (string.IsNullOrEmpty(text) || text == "0")
{
bLayer?.RemoveFromSuperLayer();
objc_setAssociatedObject(barButtonItem.Handle, BadgeKey.Handle, new CAShapeLayer().Handle, AssociationPolicy.ASSIGN);
return;
}
var textLayer = bLayer?.Sublayers?.First(p => p is CATextLayer) as CATextLayer;
if (textLayer != null)
{
textLayer.String = text;
}
else
{
barButtonItem.AddBadge(text, backgroundColor, textColor);
}
}
}
}
ToolbarItemBadgeService.cs
using TeamCollaXform.Views.Services;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: Dependency(typeof(ToolbarItemBadgeService))]
namespace TeamCollaXform.Views.Services
{
/// <summary>
///
/// </summary>
public interface IToolbarItemBadgeService
{
void SetBadge(Page page, ToolbarItem item, string value, Color backgroundColor, Color textColor);
}
/// <summary>
///
/// </summary>
public class ToolbarItemBadgeService : IToolbarItemBadgeService
{
public void SetBadge(Page page, ToolbarItem item, string value, Color backgroundColor, Color textColor)
{
Device.BeginInvokeOnMainThread(() =>
{
var renderer = Platform.GetRenderer(page);
if (renderer == null)
{
renderer = Platform.CreateRenderer(page);
Platform.SetRenderer(page, renderer);
}
var vc = renderer.ViewController;
var rightButtomItems = vc?.ParentViewController?.NavigationItem?.RightBarButtonItems;
var idx = rightButtomItems.Length - page.ToolbarItems.IndexOf(item) - 1; //Revert
if (rightButtomItems != null && rightButtomItems.Length > idx)
{
var barItem = rightButtomItems[idx];
if (barItem != null)
{
barItem.UpdateBadge(value, backgroundColor.ToUIColor(), textColor.ToUIColor());
}
}
});
}
}
}
Usage
void OnAttachClicked(object sender, EventArgs e)
{
//var answer = await DisplayAlert("Question?", "Would you like to play a game", "Yes", "No");
//Debug.WriteLine("Answer: " + answer);
ToolbarItem cmdItem = sender as ToolbarItem;
DependencyService.Get<IToolbarItemBadgeService>().SetBadge(this, cmdItem, $"2", Color.DarkOrange, Color.White);
}
Links: 1) for instruction and 2) for sample code
https://www.xamboy.com/2018/03/08/adding-badge-to-toolbaritem-in-xamarin-forms/
https://github.com/CrossGeeks/ToolbarItemBadgeSample

android RecyclerView onScrolled not call

I want to load last chapter when pull down and load next chapter when pull up,but get some problems, when I inited the first page ,then I pull up to load last chapter, the recyclerview not call onScrolled. But if I pull down small distance then pull down ,I can get the result ,here is my code:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled (RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(dy < 0 && mLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
getNextChapter();
}
if(dy > 0 &&
mLayoutManager.findLastCompletelyVisibleItemPosition() ==
mAdapter.getItemCount() - 1) {
getNextChapter();
}
Logger.i("onScrolled:" + dy);
}
});

Libgdx null pointer 2d jump and run game

I want to implement in my 2d jump and run game that my player can shoot but I always get a null pointer exception.
Does anyone know why?
In this class I check for input:
public class InputIngame implements InputProcessor {
Player player;
public void handleInput(){
//control our player using immediate impulses
if (Gdx.input.isKeyJustPressed(Input.Keys.W) && PlayScreen.player.b2body.getLinearVelocity().y == 0)
PlayScreen.player.b2body.applyLinearImpulse(new Vector2(0, 6f), PlayScreen.player.b2body.getWorldCenter(), true);
if (Gdx.input.isKeyPressed(Input.Keys.D) && PlayScreen.player.b2body.getLinearVelocity().x <= 2)
PlayScreen.player.b2body.applyLinearImpulse(new Vector2(0.2f, 0), PlayScreen.player.b2body.getWorldCenter(), true);
if (Gdx.input.isKeyPressed(Input.Keys.A) && PlayScreen.player.b2body.getLinearVelocity().x >= -2)
PlayScreen.player.b2body.applyLinearImpulse(new Vector2(-0.2f, 0), PlayScreen.player.b2body.getWorldCenter(), true);
if (Gdx.input.isKeyPressed(Input.Keys.D) && Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) && PlayScreen.player.b2body.getLinearVelocity().x > 1)
PlayScreen.player.b2body.setLinearVelocity(1, PlayScreen.player.b2body.getLinearVelocity().y);
if (Gdx.input.isKeyPressed(Input.Keys.A) && Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) && PlayScreen.player.b2body.getLinearVelocity().x < -1)
PlayScreen.player.b2body.setLinearVelocity(-1, PlayScreen.player.b2body.getLinearVelocity().y);
if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE))
player = new Player();
player.fire();
}
in my screen class I draw the fireball:
#Override
public void render(float delta) {
//separate our update logic from render
update(delta);
//Clear the game screen with Black
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//render our game map
renderer.render();
if (player.isDead == true)
player.die();
player.draw(runner.batch);
//renderer our Box2DDebugLines
b2dr.render(world, gamecam.combined);
runner.batch = new SpriteBatch();
runner.batch.setProjectionMatrix(gamecam.combined);
runner.batch.begin();
player.draw(runner.batch);
runner.batch.end();
if (TimeUtils.timeSinceNanos(startTime) > 1000000000) {
startTime = TimeUtils.nanoTime();
}
Gdx.app.log("FPSLogger", "fps: " + Gdx.graphics.getFramesPerSecond());
}
In my player class I set my variables:
private PlayScreen screen;
private Array<FireBall> fireballs;
And init them:
public Player(PlayScreen screen){
//initialize default values
runner = new HardwareRunner();
this.screen = screen;
fireballs = new Array<FireBall>();
And this method is execudet in the render method:
public void update(float dt){
//update our sprite to correspond with the position of our Box2D body
setPosition(b2body.getPosition().x - getWidth() / 2, b2body.getPosition().y - getHeight() / 2);
//update sprite with the correct frame depending on marios current action
setRegion(getFrame(dt));
for(FireBall ball : fireballs) {
ball.update(dt);
}
}
And there are also these methods:
public void fire(){
fireballs.add(new FireBall(screen, b2body.getPosition().x, b2body.getPosition().y, runningRight ? true : false));
}
public void draw(Batch batch){
super.draw(batch);
for(FireBall ball : fireballs)
ball.draw(batch);
}
And then theres my fireball class:
public class FireBall extends Sprite {
PlayScreen screen;
World world;
Array<TextureRegion> frames;
Animation fireAnimation;
float stateTime;
boolean destroyed;
boolean setToDestroy;
boolean fireRight;
Body b2body;
public FireBall(PlayScreen screen, float x, float y, boolean fireRight){
this.fireRight = fireRight;
this.screen = screen;
this.world = screen.getWorld();
frames = new Array<TextureRegion>();
for(int i = 0; i < 4; i++){
frames.add(new TextureRegion(screen.getAtlas().findRegion("fireball"), i * 8, 0, 8, 8));
}
fireAnimation = new Animation(0.2f, frames);
setRegion(fireAnimation.getKeyFrame(0));
setBounds(x, y, 6 / HardwareRunner.PPM, 6 / HardwareRunner.PPM);
defineFireBall();
}
public void defineFireBall(){
BodyDef bdef = new BodyDef();
bdef.position.set(fireRight ? getX() + 12 /HardwareRunner.PPM : getX() - 12 /HardwareRunner.PPM, getY());
bdef.type = BodyDef.BodyType.DynamicBody;
if(!world.isLocked())
b2body = world.createBody(bdef);
FixtureDef fdef = new FixtureDef();
CircleShape shape = new CircleShape();
shape.setRadius(3 / HardwareRunner.PPM);
fdef.filter.categoryBits = HardwareRunner.PROJECTILE_BIT;
fdef.filter.maskBits = HardwareRunner.GROUND_BIT |
HardwareRunner.BRICK_BIT |
HardwareRunner.OBJECT_BIT;
fdef.shape = shape;
fdef.restitution = 1;
fdef.friction = 0;
b2body.createFixture(fdef).setUserData(this);
b2body.setLinearVelocity(new Vector2(fireRight ? 2 : -2, 2.5f));
}
public void update(float dt){
stateTime += dt;
setRegion(fireAnimation.getKeyFrame(stateTime, true));
setPosition(b2body.getPosition().x - getWidth() / 2, b2body.getPosition().y - getHeight() / 2);
if((stateTime > 3 || setToDestroy) && !destroyed) {
world.destroyBody(b2body);
destroyed = true;
}
if(b2body.getLinearVelocity().y > 2f)
b2body.setLinearVelocity(b2body.getLinearVelocity().x, 2f);
if((fireRight && b2body.getLinearVelocity().x < 0) || (!fireRight && b2body.getLinearVelocity().x > 0))
setToDestroy();
}
public void setToDestroy(){
setToDestroy = true;
}
public boolean isDestroyed(){
return destroyed;
}
}
But if I try to start the game I get this error:
Exception in thread "LWJGL Application" java.lang.NullPointerException
at de.tobls.hardwarerunner.Input.InputIngame.handleInput(InputIngame.java:30)
at de.tobls.hardwarerunner.Screens.PlayScreen.update(PlayScreen.java:109)
at de.tobls.hardwarerunner.Screens.PlayScreen.render(PlayScreen.java:130)
at com.badlogic.gdx.Game.render(Game.java:46)
at de.tobls.hardwarerunner.HardwareRunner.render(HardwareRunner.java:71)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:215)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:120)
line 30 is player.fire();
line 109 is input.handleInput();
line 130 is update(delta);
and line 71 is update(delta); in my main class
I hope anyone can help me!
i think your problem is you forget brakets in "if" condition try this :
if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) {
player = new Player();
player.fire();
}
good luck ;
if any trouble leave a comment ;)