Probably more than one way to accomplish this, but my method of choice was to create a plugin for \Magento\Shipping\Model\Shipping, specifically the collectRates() function. Granted our requirements were more specific than yours (at bottom).
Basic logic flow is...
collectRates() (Unmodified function in \Magento\Shipping\Model\Shipping, Collects rates for all shipping methods)
afterCollectRates() (plugin)
- At this point all shipping methods have been called and rates stored in our $request object.
- You can determine products that are in the cart via $request->getAllItems()
- NOTE: Child/Parent products are separate items, and depending on your store config, one or the other may not have the custom attribute you want to look at.
- You can see all shipping methods/rates via $request->getResult()->getAllRates()
- I did not find a core function to remove a rate, my workaround was to...
- Unset all data in rate to remove
- After all rates are modified, use a foreach() loop to store them in a tempArray (with some logic to not add if cost==0, etc.)
- Now flush and reset all existing rates via $request->getResult()->reset()
- Finally, add the rates from your tempArray back in
Depending on how you calculate rates, you may also want to extend the various shipping methods so you can bypass them entirely when certain products are in the cart (probably not for your use case, but for anyone trying to disable UPS/FedEx/etc. rates)
As mentioned, our requirements were more extensive and we also had a beforeCollectRates() function which actually created the product array and some other logic (we had to restrict various shipping methods, add handling for specific products, and use dimensional logic to create a list of shipping boxes to send to UPS/FedEx, etc. for the actual CollectRates() part.)