I'm trying to create something like this https://miro.medium.com/max/162/1*yMtMLC3mdjndmVHzPY5Adw.png in xamarin forms tabbed page and I'm copying the code of this https://medium.com/#androidtutorial/draw-custom-shapes-in-curved-bottom-navigation-view-361c9a5507dd into my TabbedPageRenderer. The problem is that I'm not getting any result.
Code.
public partial class TabbedPages : TabbedPage
{
public TabbedPages ()
{
InitializeComponent();
this.Children.Add(new Page()
{
Title = "Home",
Icon = "homepage_icon"
});
this.Children.Add(new Page()
{
Title = "Home",
Icon = "homepage_icon"
});
this.Children.Add(new Page()
{
Title = "Home",
Icon = "homepage_icon"
});
this.Children.Add(new Page()
{
Title = "Home",
Icon = "homepage_icon"
});
}
Renderer.
[assembly: ExportRenderer(typeof(TabbedPages), typeof(BottomNavTabPageRenderer))]
namespace App.Droid.CustomRenderer
{
public class BottomNavTabPageRenderer : TabbedPageRenderer
{
private bool _isShiftModeSet;
private Path mPath;
private Paint mPaint;
private int CURVE_CIRCLE_RADIUS = 90;
// the coordinates of the first curve
private Android.Graphics.Point mFirstCurveStartPoint = new Android.Graphics.Point();
private Android.Graphics.Point mFirstCurveEndPoint = new Android.Graphics.Point();
private Android.Graphics.Point mFirstCurveControlPoint1 = new Android.Graphics.Point();
private Android.Graphics.Point mFirstCurveControlPoint2 = new Android.Graphics.Point();
//the coordinates of the second curve
private Android.Graphics.Point mSecondCurveStartPoint = new Android.Graphics.Point();
private Android.Graphics.Point mSecondCurveEndPoint = new Android.Graphics.Point();
private Android.Graphics.Point mSecondCurveControlPoint1 = new Android.Graphics.Point();
private Android.Graphics.Point mSecondCurveControlPoint2 = new Android.Graphics.Point();
private int mNavigationBarWidth;
private int mNavigationBarHeight;
public BottomNavTabPageRenderer(Context context)
: base(context)
{
init();
}
private void init()
{
mPath = new Path();
mPaint = new Paint();
mPaint.SetStyle(Paint.Style.FillAndStroke);
mPaint.Color = Android.Graphics.Color.White;
SetBackgroundColor(Android.Graphics.Color.Transparent);
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
BottomNavigationMenuView bottomNavigationMenuView = FindChildOfType<BottomNavigationMenuView>(ViewGroup);
mNavigationBarWidth = bottomNavigationMenuView.Width; // Width I'm getting is 720
mNavigationBarHeight = bottomNavigationMenuView.Height; // Height I'm getting is 112
// the coordinates (x,y) of the start point before curve
mFirstCurveStartPoint.Set((mNavigationBarWidth / 2) - (CURVE_CIRCLE_RADIUS * 2) - (CURVE_CIRCLE_RADIUS / 3), 0);
// the coordinates (x,y) of the end point after curve
mFirstCurveEndPoint.Set(mNavigationBarWidth / 2, CURVE_CIRCLE_RADIUS + (CURVE_CIRCLE_RADIUS / 4));
// same thing for the second curve
mSecondCurveStartPoint = mFirstCurveEndPoint;
mSecondCurveEndPoint.Set((mNavigationBarWidth / 2) + (CURVE_CIRCLE_RADIUS * 2) + (CURVE_CIRCLE_RADIUS / 3), 0);
// the coordinates (x,y) of the 1st control point on a cubic curve
mFirstCurveControlPoint1.Set(mFirstCurveStartPoint.X + CURVE_CIRCLE_RADIUS + (CURVE_CIRCLE_RADIUS / 4), mFirstCurveStartPoint.Y);
// the coordinates (x,y) of the 2nd control point on a cubic curve
mFirstCurveControlPoint2.Set(mFirstCurveEndPoint.X - (CURVE_CIRCLE_RADIUS * 2) + CURVE_CIRCLE_RADIUS, mFirstCurveEndPoint.Y);
mSecondCurveControlPoint1.Set(mSecondCurveStartPoint.X + (CURVE_CIRCLE_RADIUS * 2) - CURVE_CIRCLE_RADIUS, mSecondCurveStartPoint.Y);
mSecondCurveControlPoint2.Set(mSecondCurveEndPoint.X - (CURVE_CIRCLE_RADIUS + (CURVE_CIRCLE_RADIUS / 4)), mSecondCurveEndPoint.Y);
mPath.Reset();
mPath.MoveTo(0, 0);
mPath.LineTo(mFirstCurveStartPoint.X, mFirstCurveStartPoint.Y);
mPath.CubicTo(mFirstCurveControlPoint1.X, mFirstCurveControlPoint1.Y,
mFirstCurveControlPoint2.X, mFirstCurveControlPoint2.Y,
mFirstCurveEndPoint.X, mFirstCurveEndPoint.Y);
mPath.CubicTo(mSecondCurveControlPoint1.X, mSecondCurveControlPoint1.Y,
mSecondCurveControlPoint2.X, mSecondCurveControlPoint2.Y,
mSecondCurveEndPoint.X, mSecondCurveEndPoint.Y);
mPath.LineTo(mNavigationBarWidth, 0);
mPath.LineTo(mNavigationBarWidth, mNavigationBarHeight);
mPath.LineTo(0, mNavigationBarHeight);
mPath.Close();
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
canvas.DrawPath(mPath, mPaint);
}
private T FindChildOfType<T>(ViewGroup viewGroup) where T : Android.Views.View
{
if (viewGroup == null || viewGroup.ChildCount == 0) return null;
for (var i = 0; i < viewGroup.ChildCount; i++)
{
var child = viewGroup.GetChildAt(i);
var typedChild = child as T;
if (typedChild != null) return typedChild;
if (!(child is ViewGroup)) continue;
var result = FindChildOfType<T>(child as ViewGroup);
if (result != null) return result;
}
return null;
}
}}
Any help would be appreciated!
Related
I have a widget made up of 2 TextViews, 2 ImageViews, and 2 TextClocks. I am trying to update the widget using a ScreenListener (code below) and an alarm. I want all views to update when the screen is turned On. Also, a repeating alarm is set when the screen is turned On. The alarm is used to trigger the update of the battery level indicator (an ImageView). The image for the battery level indicator is generated using the ProgressRing (code below). The alarm is cancelled when the screen is turned Off. The TextClocks take care of themselves.
Everything works when debugging using the emulator or my phone. The various components update when I turn the screen On and Off. However, when I install the widget on my phone, the widget stops updating after a period of time. Initially, I can turn the screen On and Off and everything updates. And, I can plug (or unplug) the phone and the alarm will update the battery level indicator. But, after some period of time (I think with the screen Off), updating stops. Turning the screen On no longer updates anything and, I beleive, the alarm is no longer set. (Note: The TextClock continue to work and show the correct time.)
When debugging, I have seen the instances of the ScreenLister and ProgressRing become null. So, I've included checks for this and make new instances when it happens. This doesn't seem like the right solution. Should this be occurring?
Is the system killing my widget? Is there a way around this, if so? Is my AppWidgetProvider somehow losing it's connection to the RemoteViews?
Thanks for any help. And, let me know if you need more information.
P.S. I'm currently building for Android 9.0. And, I'm new to Android programming.
AppWidgetProvider
namespace ClockCalBattery
{
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new string[] { "android.appwidget.action.APPWIDGET_UPDATE", Intent.ActionUserPresent})]
[MetaData("android.appwidget.provider", Resource = "#xml/appwidgetprovider")]
public class CCBWidget : AppWidgetProvider, ScreenStateListener
{
public static ProgressRing progressRing = new ProgressRing();
private static String ACTION_UPDATE_BATTERY = "com.lifetree.clockcalbattery.UPDATE-BATTERY";
public static ScreenListener mScreenListener;
public static int n_alarms = 0;
public override void OnReceive(Context context, Intent intent)
{
base.OnReceive(context, intent);
if (intent.Action == ACTION_UPDATE_BATTERY)
{
update_batteryLevel(context);
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
}
}
public override void OnEnabled(Context context)
{
base.OnEnabled(context);
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
update_batteryLevel(Application.Context);
update_nextAlarm(Application.Context);
}
public override void OnDisabled(Context context)
{
base.OnDisabled(context);
}
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
base.OnUpdate(context, appWidgetManager, appWidgetIds);
mScreenListener = new ScreenListener(context);
progressRing = new ProgressRing();
var me = new ComponentName(context, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var widgetView = BuildRemoteViews(context, appWidgetIds);
appWidgetManager.UpdateAppWidget(me, widgetView);
}
private RemoteViews BuildRemoteViews(Context context, int[] appWidgetIds)
{
RemoteViews widgetView = new RemoteViews(context.PackageName, Resource.Layout.CCBWidget);
update_TodaysDate(widgetView);
update_nextAlarm(context, widgetView);
update_batteryLevel(widgetView);
return widgetView;
}
private void update_TodaysDate(RemoteViews widgetView)
{
string today = DateTime.Today.ToString("D");
widgetView.SetTextViewText(Resource.Id.longDate, today);
}
private void update_nextAlarm(Context context, RemoteViews widgetView)
{
AlarmManager am = (AlarmManager)context.GetSystemService(Context.AlarmService);
AlarmManager.AlarmClockInfo alarmInfo = am.NextAlarmClock;
if (alarmInfo != null)
{
long time = alarmInfo.TriggerTime;
DateTime alarmDateTime = new DateTime(1970, 1, 1).AddMilliseconds(time).ToLocalTime();
string alarmTime = alarmDateTime.ToString("ddd ");
alarmTime += alarmDateTime.ToString("t");
widgetView.SetTextViewText(Resource.Id.textView_alarmTime, alarmTime);
widgetView.SetImageViewResource(Resource.Id.imageView_alarmIcon, Resource.Drawable.whiteOnBlack_clock);
}
else
{
widgetView.SetTextViewText(Resource.Id.textView_alarmTime, "");
widgetView.SetImageViewResource(Resource.Id.imageView_alarmIcon, Resource.Drawable.navigation_empty_icon);
}
}
private void update_nextAlarm(Context mContext)
{
RemoteViews widgetView = new RemoteViews(mContext.PackageName, Resource.Layout.CCBWidget);
update_nextAlarm(mContext, widgetView);
var me = new ComponentName(mContext, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var awm = AppWidgetManager.GetInstance(mContext);
awm.UpdateAppWidget(me, widgetView);
}
private void update_batteryLevel(RemoteViews widgetView)
{
progressRing.setProgress((int)(Battery.ChargeLevel * 100.0));
progressRing.drawProgressBitmap();
widgetView.SetImageViewBitmap(Resource.Id.progressRing, progressRing.ring);
}
private void update_batteryLevel(Context mContext)
{
RemoteViews widgetView = new RemoteViews(mContext.PackageName, Resource.Layout.CCBWidget);
update_batteryLevel(widgetView);
var me = new ComponentName(mContext, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var awm = AppWidgetManager.GetInstance(mContext);
awm.UpdateAppWidget(me, widgetView);
}
public static void turnUpdateAlarmOnOff(Context context, bool turnOn, int time)
{
AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);
Intent intent = new Intent(context, typeof(CCBWidget));
intent.SetAction(ACTION_UPDATE_BATTERY);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, 0, intent, 0);
if (turnOn)
{
// Add extra 1 sec because sometimes ACTION_BATTERY_CHANGED is called after the first alarm
alarmManager.SetInexactRepeating(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 1000, time * 1000, pendingIntent);
n_alarms++;
}
else
{
alarmManager.Cancel(pendingIntent);
n_alarms--;
}
}
public void onScreenOn()
{
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(Application.Context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
update_batteryLevel(Application.Context);
update_nextAlarm(Application.Context);
turnUpdateAlarmOnOff(Application.Context, true, 60);
}
public void onScreenOff()
{
turnUpdateAlarmOnOff(Application.Context, false, 1);
}
public void onUserPresent()
{
//Console.WriteLine("onUserPresent");
}
}
}
ScreenListener
namespace ClockCalBattery
{
public class ScreenListener
{
private Context mContext;
private ScreenBroadcastReceiver mScreenReceiver;
private static ScreenStateListener mScreenStateListener;
public ScreenListener(Context context)
{
mContext = context;
mScreenReceiver = new ScreenBroadcastReceiver();
}
/**
* screen BroadcastReceiver
*/
private class ScreenBroadcastReceiver : BroadcastReceiver
{
private String action = null;
public override void OnReceive(Context context, Intent intent)
{
action = intent.Action;
if (Intent.ActionScreenOn == action)
{ // screen on
mScreenStateListener.onScreenOn();
}
else if (Intent.ActionScreenOff == action)
{ // screen off
mScreenStateListener.onScreenOff();
}
else if (Intent.ActionUserPresent == action)
{ // unlock
mScreenStateListener.onUserPresent();
}
}
}
/**
* begin to listen screen state
*
* #param listener
*/
public void begin(ScreenStateListener listener)
{
mScreenStateListener = listener;
registerListener();
getScreenState();
}
/**
* get screen state
*/
private void getScreenState()
{
PowerManager manager = (PowerManager)mContext
.GetSystemService(Context.PowerService);
if (manager.IsInteractive)
{
if (mScreenStateListener != null)
{
mScreenStateListener.onScreenOn();
}
}
else
{
if (mScreenStateListener != null)
{
mScreenStateListener.onScreenOff();
}
}
}
/**
* stop listen screen state
*/
public void unregisterListener()
{
mContext.UnregisterReceiver(mScreenReceiver);
}
/**
* regist screen state broadcast
*/
private void registerListener()
{
IntentFilter filter = new IntentFilter();
filter.AddAction(Intent.ActionScreenOn);
filter.AddAction(Intent.ActionScreenOff);
filter.AddAction(Intent.ActionUserPresent);
mContext.ApplicationContext.RegisterReceiver(mScreenReceiver, filter);
//mContext.RegisterReceiver(mScreenReceiver, filter);
}
public interface ScreenStateListener
{// Returns screen status information to the caller
void onScreenOn();
void onScreenOff();
void onUserPresent();
}
}
}
ProgressRing
namespace ClockCalBattery
{
public class ProgressRing
{
private int max = 100;
public int progress;
private Path path = new Path();
Color color = new Color(50, 50, 255, 255);
private Paint paint;
private Paint mPaintProgress;
private RectF mRectF;
private Paint batteryLevelTextPaint;
private Paint batteryStateTextPaint;
private String batteryLevelText = "0%";
private String batteryStateText = null;
private BatteryState bs;
private Rect textBounds = new Rect();
private int centerY;
private int centerX;
private float swipeAndgle = 0;
public Bitmap ring;
private int bitmapWidth = 70;
private int bitmapHeight = 70;
public ProgressRing()
{
progress = -1;
paint = new Paint();
paint.AntiAlias = true;
paint.StrokeWidth = 1;
paint.SetStyle(Paint.Style.Stroke);
paint.Color = color;
mPaintProgress = new Paint();
mPaintProgress.AntiAlias = true;
mPaintProgress.SetStyle(Paint.Style.Stroke);
mPaintProgress.StrokeWidth = 5;
mPaintProgress.Color = color;
batteryLevelTextPaint = new Paint();
batteryLevelTextPaint.AntiAlias = true;
batteryLevelTextPaint.SetStyle(Paint.Style.Fill);
batteryLevelTextPaint.Color = color;
batteryLevelTextPaint.StrokeWidth = 1;
batteryStateTextPaint = new Paint();
batteryStateTextPaint.AntiAlias = true;
batteryStateTextPaint.SetStyle(Paint.Style.Fill); ;
batteryStateTextPaint.Color = color;
batteryStateTextPaint.StrokeWidth = 1;
//batteryStateTextPaint.SetTypeface(Typeface.Create(Typeface.Default, TypefaceStyle.Bold));
Init_ring();
}
private void Init_ring()
{
int viewWidth = bitmapWidth;
int viewHeight = bitmapHeight;
float radius = (float)(bitmapHeight / 2.0);
path.Reset();
centerX = viewWidth / 2;
centerY = viewHeight / 2;
path.AddCircle(centerX, centerY, radius, Path.Direction.Cw);
float smallCirclRadius = radius - (float)(0.1 * radius);
path.AddCircle(centerX, centerY, smallCirclRadius, Path.Direction.Cw);
//mRectF = new RectF(0, 0, viewWidth, viewHeight);
mRectF = new RectF(centerX - smallCirclRadius, centerY - smallCirclRadius, centerX + smallCirclRadius, centerY + smallCirclRadius);
batteryLevelTextPaint.TextSize = radius * 0.5f;
batteryStateTextPaint.TextSize = radius * 0.30f;
}
internal void setProgress(int progress)
{
this.progress = progress;
int percentage = progress * 100 / max;
swipeAndgle = percentage * 360 / 100;
batteryLevelText = percentage + "%";
batteryStateText = null;
bs = Battery.State;
if (bs == BatteryState.Charging)
batteryStateText = "Charging";
else if (percentage > 99)
batteryStateText = "Full";
else if (percentage < 15)
batteryStateText = "Low";
}
internal void drawProgressBitmap()
{
ring = Bitmap.CreateBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.Argb8888);
Canvas c = new Canvas(ring);
byte r = 0;
byte g = 0;
byte b = 0;
byte a = 255;
hls2rgb(swipeAndgle * (120.0 / 360.0), 1, 128, ref r, ref g, ref b);
paint.Color = new Color(r, g, b, a);
mPaintProgress.Color = new Color(r, g, b, a);
batteryLevelTextPaint.Color = new Color(r, g, b, a);
batteryStateTextPaint.Color = new Color(r, g, b, a);
c.DrawArc(mRectF, 270, swipeAndgle, false, mPaintProgress);
drawTextCentred(c);
}
private void drawTextCentred(Canvas c)
{
batteryLevelTextPaint.GetTextBounds(batteryLevelText, 0, batteryLevelText.Length, textBounds);
c.DrawText(batteryLevelText, centerX - textBounds.ExactCenterX(), centerY - textBounds.ExactCenterY(), batteryLevelTextPaint);
if (batteryStateText != null)
{
batteryStateTextPaint.GetTextBounds(batteryStateText, 0, batteryStateText.Length, textBounds);
c.DrawText(batteryStateText, centerX - textBounds.ExactCenterX(), centerY - textBounds.ExactCenterY() + 15, batteryStateTextPaint);
}
}
public void hls2rgb(double color_wheel_angle, double tlen, byte ilum,
ref byte color_r, ref byte color_g, ref byte color_b)
{
double rlum, rm1, rm2;
if (ilum > 255) ilum = 255;
if (ilum < 0) ilum = 0;
if (tlen > 1) tlen = 1;
if (tlen < 0) tlen = 0;
rlum = (double)ilum / 255;
if (rlum < 0.5)
rm2 = rlum * (1.0 + tlen);
else
rm2 = rlum + tlen - (rlum * tlen);
rm1 = (2.0 * rlum) - rm2;
if (tlen == 0)
{
color_r = ilum;
color_g = ilum;
color_b = ilum;
}
else
{
color_g = (byte)(value(rm1, rm2, color_wheel_angle + 120) * 255);
color_b = (byte)(value(rm1, rm2, color_wheel_angle) * 255);
color_r = (byte)(value(rm1, rm2, color_wheel_angle - 120) * 255);
}
return;
}
public double value(double r1, double r2, double angle)
{
double value;
angle = angle - 120;
if (angle > 360) angle = angle - 360;
if (angle < 0) angle = angle + 360;
if (angle < 60)
value = r1 + (r2 - r1) * angle / 60;
else if (angle < 180)
value = r2;
else if (angle < 240)
value = r1 + (r2 - r1) * (240 - angle) / 60;
else
value = r1;
return (value);
}
}
}
I've put together a custom control in WPF in C# based on the Selector primitive:
public class PadControl : Selector
{
private const int padWidth = 28;
private const int padHeight = 18;
private const int padGap = 5;
private double _width;
private double _height;
static PadControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(PadControl), new FrameworkPropertyMetadata(typeof(PadControl),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender));
}
public PadControl()
{
}
protected override Size MeasureOverride(Size constraint)
{
_width = constraint.Width;
_height = constraint.Height;
return base.MeasureOverride(constraint);
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
var numHorz = _width / (padWidth + padGap);
var numVert = Math.Min(_height / (padHeight + padGap), 16);
var pen = new Pen(Brushes.Black, 1.0);
var brush = new SolidColorBrush(Color.FromRgb(192, 192, 192));
for(int bar = 0; bar < numHorz; bar++)
{
for(int track = 0; track < numVert; track++)
{
var rect = GetPadRect(bar, track);
dc.DrawRectangle(brush, pen, rect);
}
}
if (Items == null)
return;
brush = new SolidColorBrush(Colors.LightYellow);
var typeface = new Typeface("Tahoma");
foreach(PadViewModel item in Items)
{
var rect = GetPadRect(item.Bar, item.Track);
dc.DrawRectangle(brush, pen, rect);
var formatted = new FormattedText(item.Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12, Brushes.Black, 1.0)
{
MaxTextWidth = padWidth,
TextAlignment = TextAlignment.Center
};
dc.DrawText(formatted, GetPadPoint(item.Bar, item.Track));
}
}
private Rect GetPadRect(int bar, int track)
{
var rect = new Rect(bar * (padWidth + padGap) + padGap, track * (padHeight + padGap) + padGap, padWidth, padHeight);
return rect;
}
private Point GetPadPoint(int bar, int track)
{
var point = new Point(bar * (padWidth + padGap) + padGap, track * (padHeight + padGap) + padGap);
return point;
}
}
This draws as I'd like it, but it doesn't draw the items until I resize the control.
When the control renders for the first time, it doesn't render any Items as that is empty. Items is populated indirectly with this:
<controls:PadControl Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Pads}"/>
The problem is, I don't see an update come through after ItemsSource has been set. So the question is, how do I attach an event handler to catch this? Do I attach it to Items or to ItemsSource?
The answer is to override OnItemsSourceChanged as described by Keith Stein in the comments above.
I'm using MaterialSkin UI controls , how can I change the control properties to Right to left, because it's by default Left to Right.
I think this is the code should be edited :
private void UpdateTabRects()
{
_tabRects = new List<Rectangle>();
//If there isn't a base tab control, the rects shouldn't be calculated
//If there aren't tab pages in the base tab control, the list should just be empty which has been set already; exit the void
if (_baseTabControl == null || _baseTabControl.TabCount == 0) return;
//Calculate the bounds of each tab header specified in the base tab control
using (var b = new Bitmap(1, 1))
{
using (var g = Graphics.FromImage(b))
{
_tabRects.Add(new Rectangle(SkinManager.FormPadding, 0, TabHeaderPadding * 2 + (int)g.MeasureString(_baseTabControl.TabPages[0].Text, SkinManager.Font_Size11).Width, Height));
for (int i = 1; i < _baseTabControl.TabPages.Count; i++)
{
_tabRects.Add(new Rectangle(_tabRects[i - 1].Right, 0, TabHeaderPadding * 2 + (int)g.MeasureString(_baseTabControl.TabPages[i].Text , SkinManager.Font_Size11).Width , Height));
}
}
}
}
and this is the full code for the control :
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms;
using MaterialSkin.Animations;
namespace MaterialSkin.Controls
{
public class MaterialTabSelector : Control, IMaterialControl
{
[Browsable(false)]
public int Depth { get; set; }
[Browsable(false)]
public MaterialSkinManager SkinManager => MaterialSkinManager.Instance;
[Browsable(false)]
public MouseState MouseState { get; set; }
private MaterialTabControl _baseTabControl;
public MaterialTabControl BaseTabControl
{
get { return _baseTabControl; }
set
{
_baseTabControl = value;
if (_baseTabControl == null) return;
_previousSelectedTabIndex = _baseTabControl.SelectedIndex;
_baseTabControl.Deselected += (sender, args) =>
{
_previousSelectedTabIndex = _baseTabControl.SelectedIndex;
};
_baseTabControl.SelectedIndexChanged += (sender, args) =>
{
_animationManager.SetProgress(0);
_animationManager.StartNewAnimation(AnimationDirection.In);
};
_baseTabControl.ControlAdded += delegate
{
Invalidate();
};
_baseTabControl.ControlRemoved += delegate
{
Invalidate();
};
}
}
private int _previousSelectedTabIndex;
private Point _animationSource;
private readonly AnimationManager _animationManager;
private List<Rectangle> _tabRects;
private const int TabHeaderPadding = 24;
private const int TabIndicatorHeight = 2;
public MaterialTabSelector()
{
SetStyle(ControlStyles.DoubleBuffer | ControlStyles.OptimizedDoubleBuffer, true);
Height = 48;
_animationManager = new AnimationManager
{
AnimationType = AnimationType.EaseOut,
Increment = 0.04
};
_animationManager.OnAnimationProgress += sender => Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.TextRenderingHint = TextRenderingHint.AntiAlias;
g.Clear(SkinManager.ColorScheme.PrimaryColor);
if (_baseTabControl == null) return;
if (!_animationManager.IsAnimating() || _tabRects == null || _tabRects.Count != _baseTabControl.TabCount)
UpdateTabRects();
var animationProgress = _animationManager.GetProgress();
//Click feedback
if (_animationManager.IsAnimating())
{
var rippleBrush = new SolidBrush(Color.FromArgb((int)(51 - (animationProgress * 50)), Color.White));
var rippleSize = (int)(animationProgress * _tabRects[_baseTabControl.SelectedIndex].Width * 1.75);
g.SetClip(_tabRects[_baseTabControl.SelectedIndex]);
g.FillEllipse(rippleBrush, new Rectangle(_animationSource.X - rippleSize / 2, _animationSource.Y - rippleSize / 2, rippleSize, rippleSize));
g.ResetClip();
rippleBrush.Dispose();
}
//Draw tab headers
foreach (TabPage tabPage in _baseTabControl.TabPages)
{
var currentTabIndex = _baseTabControl.TabPages.IndexOf(tabPage);
Brush textBrush = new SolidBrush(Color.FromArgb(CalculateTextAlpha(currentTabIndex, animationProgress), SkinManager.ColorScheme.TextColor));
g.DrawString(tabPage.Text.ToUpper(), SkinManager.Font_Size11, textBrush, _tabRects[currentTabIndex], new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
textBrush.Dispose();
}
//Animate tab indicator
var previousSelectedTabIndexIfHasOne = _previousSelectedTabIndex == -1 ? _baseTabControl.SelectedIndex : _previousSelectedTabIndex;
var previousActiveTabRect = _tabRects[previousSelectedTabIndexIfHasOne];
var activeTabPageRect = _tabRects[_baseTabControl.SelectedIndex];
var y = activeTabPageRect.Bottom - 2;
var x = previousActiveTabRect.X + (int)((activeTabPageRect.X - previousActiveTabRect.X) * animationProgress);
var width = previousActiveTabRect.Width + (int)((activeTabPageRect.Width - previousActiveTabRect.Width) * animationProgress);
g.FillRectangle(SkinManager.ColorScheme.AccentBrush, x, y, width, TabIndicatorHeight);
}
private int CalculateTextAlpha(int tabIndex, double animationProgress)
{
int primaryA = SkinManager.ActionBarText.A;
int secondaryA = SkinManager.ActionBarTextSecondary.A;
if (tabIndex == _baseTabControl.SelectedIndex && !_animationManager.IsAnimating())
{
return primaryA;
}
if (tabIndex != _previousSelectedTabIndex && tabIndex != _baseTabControl.SelectedIndex)
{
return secondaryA;
}
if (tabIndex == _previousSelectedTabIndex)
{
return primaryA - (int)((primaryA - secondaryA) * animationProgress);
}
return secondaryA + (int)((primaryA - secondaryA) * animationProgress);
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (_tabRects == null) UpdateTabRects();
for (var i = 0; i < _tabRects.Count; i++)
{
if (_tabRects[i].Contains(e.Location))
{
_baseTabControl.SelectedIndex = i;
}
}
_animationSource = e.Location;
}
private void UpdateTabRects()
{
_tabRects = new List<Rectangle>();
//If there isn't a base tab control, the rects shouldn't be calculated
//If there aren't tab pages in the base tab control, the list should just be empty which has been set already; exit the void
if (_baseTabControl == null || _baseTabControl.TabCount == 0) return;
//Calculate the bounds of each tab header specified in the base tab control
using (var b = new Bitmap(1, 1))
{
using (var g = Graphics.FromImage(b))
{
_tabRects.Add(new Rectangle(SkinManager.FormPadding, 0, TabHeaderPadding * 2 + (int)g.MeasureString(_baseTabControl.TabPages[0].Text, SkinManager.Font_Size11).Width, Height));
for (int i = 1; i < _baseTabControl.TabPages.Count; i++)
{
_tabRects.Add(new Rectangle(_tabRects[i - 1].Right, 0, TabHeaderPadding * 2 + (int)g.MeasureString(_baseTabControl.TabPages[i].Text , SkinManager.Font_Size11).Width , Height));
}
}
}
}
}
}
If you're using windows forms you would go into the properties of the tab control and make:
RightToLeft = Yes
and
RightToLeftLayout = True.
This is also a duplicate question:
How to make Managed Tab Control (MTC) appear right to left
I am working modifying an application that will be an utility. The application is designed so far to load pictures from any folder and show them in thumbnails, then the user should be able to select those that will want to save in a database. The thumbnails consists of an ImageViewer form that will load each image. Thus, in the ImageViewer form there is a textbox and a checkbox. Each of them will be generated dynamically as many pictures are loaded (see the image below). The problem is that when clicking the checkbox it should show the name listed above the picture (thumbnail textbox) of the file in a label (it can be a label or textbox). Any time when the user clicks the checkbox will see a message saying: 'Added anyImage.jpg' or when deselecting the checkbox will say 'Removed anyImage.jpg'. It is not showing the text in the label. I have the following code.
This code is to load the main form:
public MainForm()
{
InitializeComponent();
Login loginSystem = new Login();
lbHowMany.Visible = false;
lbHowMany.Text = "Images";
lbNumberOfFiles.Visible = false;
btnEnableViewer.Text = "Disable Viewer";
this.buttonCancel.Enabled = false;
//stripSelectedFile.Text = "";
m_ImageDialog = new ImageDialog();
m_AddImageDelegate = new DelegateAddImage(this.AddImage);
m_Controller = new ThumbnailController();
m_Controller.OnStart += new ThumbnailControllerEventHandler(m_Controller_OnStart);
m_Controller.OnAdd += new ThumbnailControllerEventHandler(m_Controller_OnAdd);
m_Controller.OnEnd += new ThumbnailControllerEventHandler(m_Controller_OnEnd);
if (ImageViewer.sendSelectedFile != null)
{
stripSelectedFile.Text = ImageViewer.sendSelectedFile.ToString();
txInformation.Text = ImageViewer.sendSelectedFile.ToString();
}
}
This other code is from the ImageViewer form checkbox:
public void cboxToSave_CheckedChanged(object sender, EventArgs e)
{
if (cboxToSave.Checked == true)
{
sendSelectedFile = "Added: " + txFileName.Text;
}
else
{
{
sendSelectedFile = "Removed: " + txFileName.Text;
}
}
}
This is the variable declared in the class that will send the selected file name to the main form: public static string sendSelectedFile;
ImageViewer Code:
public partial class ImageViewer : UserControl
{
private Image m_Image;
private string m_ImageLocation;
private bool m_IsThumbnail;
private bool m_IsActive;
public static string sendSelectedFile;
public ImageViewer()
{
m_IsThumbnail = false;
m_IsActive = false;
InitializeComponent();
}
public Image Image
{
set
{
m_Image = value;
}
get
{
return m_Image;
}
}
public string ImageLocation
{
set
{
m_ImageLocation = value;
}
get
{
return m_ImageLocation;
}
}
public bool IsActive
{
set
{
m_IsActive = value;
this.Invalidate();
}
get
{
return m_IsActive;
}
}
public bool IsThumbnail
{
set
{
m_IsThumbnail = value;
}
get
{
return m_IsThumbnail;
}
}
public void ImageSizeChanged(object sender, ThumbnailImageEventArgs e)
{
this.Width = e.Size;
this.Height = e.Size;
this.Invalidate();
}
public void LoadImage(string imageFilename, int width, int height)
{
Image tempImage = Image.FromFile(imageFilename);
m_ImageLocation = imageFilename;
//gets the name of the file from the location
txFileName.Text = Path.GetFileNameWithoutExtension(imageFilename);
int dw = tempImage.Width;
int dh = tempImage.Height;
int tw = width;
int th = height;
double zw = (tw / (double)dw);
double zh = (th / (double)dh);
double z = (zw <= zh) ? zw : zh;
dw = (int)(dw * z);
dh = (int)(dh * z);
m_Image = new Bitmap(dw, dh);
Graphics g = Graphics.FromImage(m_Image);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(tempImage, 0, 0, dw, dh);
g.Dispose();
tempImage.Dispose();
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
if (g == null) return;
if (m_Image == null) return;
int dw = m_Image.Width;
int dh = m_Image.Height;
int tw = this.Width - 8; // remove border, 4*4
int th = this.Height - 8; // remove border, 4*4
double zw = (tw / (double)dw);
double zh = (th / (double)dh);
double z = (zw <= zh) ? zw : zh;
dw = (int)(dw * z);
dh = (int)(dh * z);
int dl = 4 + (tw - dw) / 2; // add border 2*2
int dt = 4 + (th - dh) / 2; // add border 2*2
g.DrawRectangle(new Pen(Color.Yellow), dl, dt, dw, dh);
if (m_IsThumbnail)
for (int j = 0; j < 3; j++)
{
//draws and color the horizontal line in the miniature
g.DrawLine(new Pen(Color.LightSalmon),
new Point(dl + 3, dt + dh + 1 + j),
new Point(dl + dw + 3, dt + dh + 1 + j));
//draws and color the vertical right line in the miniature
g.DrawLine(new Pen(Color.LightGreen),
new Point(dl + dw + 1 + j, dt + 3),
new Point(dl + dw + 1 + j, dt + dh + 3));
}
g.DrawImage(m_Image, dl, dt, dw, dh);
if (m_IsActive)
{
//draws the rectangle inside and gives it color
g.DrawRectangle(new Pen(Color.MediumTurquoise, 1), dl, dt, dw, dh);
//draws the rectangle outside and gives it color
g.DrawRectangle(new Pen(Color.RosyBrown, 2), dl - 2, dt - 2, dw + 4, dh + 4);
}
}
private void OnResize(object sender, EventArgs e)
{
this.Invalidate();
}
public void cboxToSave_CheckedChanged(object sender, EventArgs e)
{
if (cboxToSave.Checked == true)
{
sendSelectedFile = "Added: " + txFileName.Text;
}
else
{
{
sendSelectedFile = "Removed: " + txFileName.Text;
}
}
}
}
Code in the MainForm that adds the images in the flowLayoutPanelMain
delegate void DelegateAddImage(string imageFilename);
private DelegateAddImage m_AddImageDelegate;
private void AddImage(string imageFilename)
{
try
{
// thread safe
if (this.InvokeRequired)
{
this.Invoke(m_AddImageDelegate, imageFilename);
}
else
{
int size = ImageSize;
lbNumberOfFiles.Visible = true;
lbHowMany.Visible = true;
ImageViewer imageViewer = new ImageViewer();
imageViewer.Dock = DockStyle.Bottom;
imageViewer.LoadImage(imageFilename, 256, 256);
imageViewer.Width = size;
imageViewer.Height = size;
imageViewer.IsThumbnail = true;
imageViewer.MouseClick += new MouseEventHandler(imageViewer_MouseClick);
txInformation.Text = imageFilename;
SetProgressBar();
counter++;
lbHowMany.Text = "Images";
lbNumberOfFiles.Text = counter.ToString();
this.OnImageSizeChanged += new ThumbnailImageEventHandler(imageViewer.ImageSizeChanged);
//passes the pictures to the main picture container
this.flowLayoutPanelMain.Controls.Add(imageViewer);
}
}
catch (Exception e)
{
MessageBox.Show("An error has ocurred. Error: " + e, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Here's a quick example of the ImageViewer Form raising a custom event whenever the checkbox is changed:
public partial class ImageViewer : Form
{
public ImageViewer()
{
InitializeComponent();
}
public delegate void dlgImageChecked(ImageViewer sender, string message);
public event dlgImageChecked ImageChecked;
private void cboxToSave_CheckedChanged(object sender, EventArgs e)
{
if (ImageChecked != null)
{
ImageChecked(this, (cboxToSave.Checked ? "Added: " : "Removed: ") + txFileName.Text);
}
}
}
Now, when you create instances of ImageViewer, you need to wire up that event...something like:
// ... in your MainForm class ...
private void button1_Click(object sender, EventArgs e)
{
// when you create your instances of ImageViewer, wire up their ImageChecked() event:
ImageViewer iv = new ImageViewer();
iv.ImageChecked += Iv_ImageChecked;
}
private void Iv_ImageChecked(ImageViewer sender, string message)
{
ImageViewer iv = (ImageViewer)sender; // if you need to reference it for other reasons ...
stripSelectedFile.Text = message;
txInformation.Text = message;
}
Your original post didn't show the creation of your ImageViewer instances so you'll need to incorporate the above somehow into your code.
I had to implement a way to navigate between different element that are stacked in xamarin.forms. In the code below, you can see that we already manage the ability to distinguish every single one of the child of the container. At the moment, what I have written works but I need some help figuring out if it’s the good way or if there is a better way or even, if I can improve what I’ve done.
Currently, when you swipe from left to right or whatever move, I just use a SimpleOnGestureListener.
Here are some samples of what we are currently achieving:
I declared my custom elements such as the container & the children and then I created the customRenderer for each of them in an android library (only working on android at the moment).
“MyContent” is simply a modified frame with a specific Draw method and an event for each move available (up, down, left, right).
Code of the container
using System;
using Xamarin.Forms;
namespace Elements
{
public class MyContainer : Layout<MyContent>
{
public MyContainer ()
{
}
protected override void LayoutChildren (double x, double y, double width, double height)
{
for (int i = 0; i < Children.Count ; i++) {
MyContent child = Children[i];
// skip invisible children
if(!child.IsVisible)
continue;
int soffset = Children.Count - i;
int eoffset = soffset - 1;
Rectangle startBoundingRegion = new Rectangle (12 * soffset + Padding.Left, 14 * soffset + Padding.Top, width - 24 * soffset, height - 8 * soffset);
Rectangle endBoundingRegion = new Rectangle (12 * eoffset + Padding.Left, 14 * eoffset + Padding.Top, width - 24 * eoffset, height - 8 * eoffset);
child.OriginalBounds = endBoundingRegion;
LayoutChildIntoBoundingRegion (child,startBoundingRegion);
child.LayoutTo(endBoundingRegion,100,Easing.Linear);
}
}
protected override SizeRequest OnSizeRequest (double widthConstraint, double heightConstraint)
{
var height = 0;
var minHeight = 0;
var width = 0;
var minWidth = 0;
for (int i = 0; i < Children.Count; i++) {
MyContent child = Children[i];
// skip invisible children
if(!child.IsVisible)
continue;
var childSizeRequest = child.GetSizeRequest (double.PositiveInfinity, height);
height = (int)Math.Max (height, childSizeRequest.Minimum.Height);
minHeight = (int)Math.Max (minHeight, childSizeRequest.Minimum.Height);
width += (int)childSizeRequest.Request.Width;
minWidth += (int)childSizeRequest.Minimum.Width;
}
return new SizeRequest (new Size (width, height), new Size (minWidth, minHeight));
}
}
}
Code of its renderer
{using ...}
[assembly: ExportRenderer (typeof(MyContainer), typeof(MyContainerRenderer))]
namespace MyApp
{
public class MyContainerRenderer : ViewRenderer<Layout<MyContent>,Android.Views.View>
{
private readonly MyContentGestureListener listener;
private readonly GestureDetector detector;
private bool isAnimating;
private MyContent currentMyContent;
public MyContainerRenderer ()
{
listener = new MyContentGestureListener();
detector = new GestureDetector(listener);
}
protected override void OnElementChanged (ElementChangedEventArgs<Layout<MyContent>> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
{
this.Touch -= HandleTouch;
listener.OnSwipeLeft -= HandleOnSwipeLeft;
listener.OnSwipeRight -= HandleOnSwipeRight;
listener.OnSwipeTop -= HandleOnSwipeTop;
listener.OnSwipeDown -= HandleOnSwipeDown;
listener.OnPanVertical -= HandleOnPanVertical;
listener.OnPanHorizontal -= HandleOnPanHorizontal;
}
if (e.OldElement == null)
{
this.Touch += HandleTouch;
listener.OnSwipeLeft += HandleOnSwipeLeft;
listener.OnSwipeRight += HandleOnSwipeRight;
listener.OnSwipeTop += HandleOnSwipeTop;
listener.OnSwipeDown += HandleOnSwipeDown;
listener.OnPanVertical += HandleOnPanVertical;
listener.OnPanHorizontal += HandleOnPanHorizontal;
}
}
void HandleTouch(object sender, TouchEventArgs e)
{
currentMyContent = ((MyContainer)this.Element).Children.Last();
if (!isAnimating) {
if (e.Event.Action == MotionEventActions.Down) {
currentMyContent.ScaleTo (1.01, 100, Easing.Linear);
} else if ( e.Event.Action == MotionEventActions.Up){
if (!isAnimating) {
isAnimating = true;
currentMyContent.ScaleTo (1, 100, Easing.Linear);
currentMyContent.LayoutTo (currentMyContent.OriginalBounds, 100, Easing.CubicIn);
isAnimating = false;
listener.ResetFlags ();
}
}
detector.OnTouchEvent (e.Event);
}
}
void HandleOnPanVertical(object sender, EventArgs distanceY)
{
Rectangle dest = new Rectangle (currentMyContent.Bounds.X,currentMyContent.Bounds.Y - ((EventArgs<float>)distanceY).Value,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
currentMyContent.Layout(dest);
}
void HandleOnPanHorizontal(object sender, EventArgs distanceX)
{
Rectangle dest = new Rectangle (currentMyContent.Bounds.X - ((EventArgs<float>)distanceX).Value,currentMyContent.Bounds.Y,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
currentMyContent.Layout (dest);
}
async void animateNext (MyContent mContent, Rectangle dest)
{
isAnimating = true;
await mContent.LayoutTo (dest, 150, Easing.Linear);
((MyContainer)this.Element).Children.Remove (mContent);
((MyContainer)this.Element).Children.Insert (0, mContent);
isAnimating = false;
}
void HandleOnSwipeLeft(object sender, EventArgs e)
{
currentMyContent.OnSwipeLeft();
Rectangle dest = new Rectangle (currentMyContent.Bounds.X,currentMyContent.Bounds.Y,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
dest.Left -= Width;
animateNext (currentMyContent, dest);
}
void HandleOnSwipeRight(object sender, EventArgs e)
{
currentMyContent.OnSwipeRight();
Rectangle dest = new Rectangle (currentMyContent.Bounds.X,currentMyContent.Bounds.Y,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
dest.Left += Width;
animateNext (currentMyContent, dest);
}
void HandleOnSwipeTop(object sender, EventArgs e)
{
Rectangle dest = new Rectangle (currentMyContent.Bounds.X, currentMyContent.Bounds.Y, currentMyContent.Bounds.Width, currentMyContent.Bounds.Height);
dest.Top -= Height;
animateNext (currentMyContent, dest);
}
void HandleOnSwipeDown(object sender, EventArgs e)
{
Rectangle dest = new Rectangle (currentMyContent.Bounds.X,currentMyContent.Bounds.Y,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
dest.Top += Height;
animateNext (currentMyContent, dest);
}
}
class MyContentGestureListener : GestureDetector.SimpleOnGestureListener
{
private static int SWIPE_THRESHOLD = 50;
private static int SWIPE_VELOCITY_THRESHOLD = 20;
private bool isPanningV;
private bool isPanningH;
public event EventHandler OnSwipeDown;
public event EventHandler OnSwipeTop;
public event EventHandler OnSwipeLeft;
public event EventHandler OnSwipeRight;
public event EventHandler OnPanHorizontal;
public event EventHandler OnPanVertical;
public override bool OnScroll(MotionEvent ev1, MotionEvent ev2,float distanceX,float distanceY)
{
if (Math.Abs (distanceX) > Math.Abs (distanceY) && OnPanHorizontal != null && !isPanningV) {
isPanningH = true;
OnPanHorizontal (this,new EventArgs<float> (distanceX));
} else if (OnPanVertical != null && !isPanningH) {
isPanningV = true;
OnPanVertical (this, new EventArgs<float> (distanceY));
}
return base.OnScroll (ev1, ev2, distanceX, distanceY);
}
public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
float diffY = e2.GetY() - e1.GetY();
float diffX = e2.GetX() - e1.GetX();
if (Math.Abs(diffX) > Math.Abs(diffY) && Math.Abs(diffX) > SWIPE_THRESHOLD && Math.Abs(velocityX) > SWIPE_VELOCITY_THRESHOLD)
{
if (diffX > 0 && OnSwipeRight != null)
{
OnSwipeRight (this, null);
isPanningH = false;
}
else if (OnSwipeLeft != null) {
OnSwipeLeft (this, null);
isPanningH = false;
}
}
else if (Math.Abs(diffY) > SWIPE_THRESHOLD && Math.Abs(velocityY) > SWIPE_VELOCITY_THRESHOLD)
{
if (diffY > 0 && OnSwipeDown != null)
{
OnSwipeDown (this, null);
isPanningV = false;
}
else if (OnSwipeTop != null) {
OnSwipeTop (this, null);
isPanningV = false;
}
}
return base.OnFling (e1, e2, velocityX, velocityY);
}
public void ResetFlags(){
isPanningH = false;
isPanningV = false;
}
}
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
Value = value;
}
public T Value { get; private set; }
}
}